From 30e15a7b8bef926162d3145861da40083901b0f2 Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Fri, 18 Aug 2017 16:02:08 -0400 Subject: [PATCH] Improved performance tracking. --- catalyst/examples/buy_and_hold_live.py | 98 ++++++++++++++----------- catalyst/exchange/algorithm_exchange.py | 12 ++- catalyst/exchange/bitfinex.py | 13 ++-- catalyst/exchange/exchange.py | 14 ++++ catalyst/utils/run_algo.py | 2 + 5 files changed, 87 insertions(+), 52 deletions(-) diff --git a/catalyst/examples/buy_and_hold_live.py b/catalyst/examples/buy_and_hold_live.py index 985c06fa..430abc18 100644 --- a/catalyst/examples/buy_and_hold_live.py +++ b/catalyst/examples/buy_and_hold_live.py @@ -1,15 +1,10 @@ -from catalyst.utils.run_algo import run_algorithm -from datetime import datetime -import pytz from logbook import Logger - +from catalyst.utils.run_algo import run_algorithm from catalyst.api import ( order, - order_target_value, order_target_percent, symbol, record, - cancel_order, get_open_orders, ) @@ -18,53 +13,67 @@ log = Logger('buy_and_hold_live') def initialize(context): log.info('initializing algo') - context.asset = symbol('eos_btc') + context.asset = symbol('eos_usd') - context.TARGET_HODL_RATIO = 0.8 - context.RESERVE_RATIO = 1.0 - context.TARGET_HODL_RATIO - - context.is_buying = True + context.TARGET_POSITIONS = 100 + context.BUY_INCREMENT = 1 def handle_data(context, data): log.info('handling bar {data}'.format(data=data)) - starting_cash = context.portfolio.starting_cash - target_hodl_value = context.TARGET_HODL_RATIO * starting_cash - reserve_value = context.RESERVE_RATIO * starting_cash - log.info('starting cash: {}'.format(starting_cash)) + cash = context.portfolio.cash + log.info('base currency available: {cash}'.format(cash=cash)) price = data.current(context.asset, 'price') - log.info('got price {}'.format(price)) + log.info('got price {price}'.format(price=price)) - # Stop buying after passing the reserve threshold - # orders = get_open_orders(context.asset) or [] - # for order in orders: - # log.info('cancelling open order {}'.format(order)) - # cancel_order(order) + orders = get_open_orders(context.asset) + if orders: + log.info('skipping bar until all open orders execute') + return - # Stop buying after passing the reserve threshold - cash = context.portfolio.cash - if cash <= reserve_value: - context.is_buying = False + if price * context.BUY_INCREMENT > cash: + log.info('not enough base currency to consider buying') + return - log.info('cash {}'.format(cash)) + is_buy = False + cost_basis = None + if context.asset in context.portfolio.positions: + position = context.portfolio.positions[context.asset] + cost_basis = position.cost_basis + log.info( + 'found {amount} positions with cost basis {cost_basis}'.format( + amount=position.amount, + cost_basis=cost_basis + ) + ) + if price < cost_basis: + is_buy = True + elif price > cost_basis * 1.1: + log.info('price higher than cost basis, taking profit') + order_target_percent( + asset=context.asset, + target=0, + limit_price=price * 0.95, + ) + else: + log.info('no buy or sell opportunity found') + else: + is_buy = True - # Check if still buying and could (approximately) afford another purchase - if context.is_buying and cash > price: - # Place order to make position in asset equal to target_hodl_value - order(context.asset, 1, limit_price=price * 1.1) - # This works - # order_target_value( - # context.asset, - # target_hodl_value, - # limit_price=price * 1.1, - # ) - # order_target_percent( - # context.asset, - # 0.01, - # limit_price=price * 1.1 - # ) + if is_buy: + log.info( + 'buying position cheaper than cost basis {} < {}'.format( + price, + cost_basis + ) + ) + order( + asset=context.asset, + amount=context.BUY_INCREMENT, + limit_price=price * 1.1 + ) record( price=price, @@ -72,6 +81,11 @@ def handle_data(context, data): starting_cash=context.portfolio.starting_cash, leverage=context.account.leverage, ) + + context.perf_tracker.update_performance() + log.info('the performance:\n{}'.format( + context.perf_tracker.to_dict('minute') + )) pass @@ -79,7 +93,7 @@ exchange_conn = dict( name='bitfinex', key='', secret=b'', - base_currency='btc' + base_currency='usd' ) run_algorithm( initialize=initialize, diff --git a/catalyst/exchange/algorithm_exchange.py b/catalyst/exchange/algorithm_exchange.py index 2e9d4b9f..23bb109b 100644 --- a/catalyst/exchange/algorithm_exchange.py +++ b/catalyst/exchange/algorithm_exchange.py @@ -40,7 +40,6 @@ class ExchangeTradingAlgorithm(TradingAlgorithm): super(self.__class__, self).__init__(*args, **kwargs) - self.perf_tracker = None log.info('exchange trading algorithm successfully initialized') def _create_clock(self): @@ -116,7 +115,9 @@ class ExchangeTradingAlgorithm(TradingAlgorithm): def handle_data(self, data): self.exchange.update_portfolio() - self.exchange.check_open_orders() + transactions = self.exchange.check_open_orders() + for transaction in transactions: + self.perf_tracker.process_transaction(transaction) if self._handle_data: self._handle_data(self, data) @@ -137,8 +138,11 @@ class ExchangeTradingAlgorithm(TradingAlgorithm): amount, style = self._calculate_order(asset, amount, limit_price, stop_price, style) - return self.exchange.order(asset, amount, limit_price, stop_price, - style) + order_id = self.exchange.order(asset, amount, limit_price, stop_price, + style) + order = self.portfolio.open_orders[order_id] + self.perf_tracker.process_order(order) + return order @api_method def batch_market_order(self, share_counts): diff --git a/catalyst/exchange/bitfinex.py b/catalyst/exchange/bitfinex.py index 8dbb1ca8..e321a799 100644 --- a/catalyst/exchange/bitfinex.py +++ b/catalyst/exchange/bitfinex.py @@ -140,12 +140,13 @@ class Bitfinex(Exchange): executed_price = float(order_status['avg_execution_price']) - if executed_price > 0 and price > 0: - # TODO: This does not really work. Find a better way. - commission = executed_price - price \ - if is_buy else price - executed_price - else: - commission = None + # if executed_price > 0 and price > 0: + # # TODO: This does not really work. Find a better way. + # commission = executed_price - price \ + # if is_buy else price - executed_price + # else: + # commission = None + commission = None date = pd.Timestamp.utcfromtimestamp(float(order_status['timestamp'])) date = pytz.utc.localize(date) diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index ce8fb5c3..f0844db2 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -5,6 +5,7 @@ from abc import ABCMeta, abstractmethod, abstractproperty import pandas as pd from catalyst.assets._assets import Asset from catalyst.finance.order import ORDER_STATUS +from catalyst.finance.transaction import Transaction from catalyst.errors import ( MultipleSymbolsFound, @@ -87,6 +88,7 @@ class Exchange: self.assets[exchange_symbol] = asset_obj def check_open_orders(self): + transactions = list() if self.portfolio.open_orders: for order_id in list(self.portfolio.open_orders): log.debug('found open order: {}'.format(order_id)) @@ -94,6 +96,17 @@ class Exchange: log.debug('got updated order {}'.format(order)) if order.status == ORDER_STATUS.FILLED: + transaction = Transaction( + asset=order.asset, + amount=order.amount, + dt=pd.Timestamp.utcnow(), + price=order.executed_price, + order_id=order.id, + commission=order.commission + ) + transactions.append(transaction) + + # TODO: use the transaction to pass the executed price self.portfolio.execute_order(order) elif order.status == ORDER_STATUS.CANCELLED: self.portfolio.remove_order(order) @@ -105,6 +118,7 @@ class Exchange: delta=delta ) ) + return transactions @abstractmethod def subscribe_to_market_data(self, symbol): diff --git a/catalyst/utils/run_algo.py b/catalyst/utils/run_algo.py index ae1e6fd2..3e271c6e 100644 --- a/catalyst/utils/run_algo.py +++ b/catalyst/utils/run_algo.py @@ -253,6 +253,8 @@ def _run(handle_data, start=start, end=end, capital_base=exchange.portfolio.starting_cash, + emission_rate='minute', + data_frequency='minute' ) # sim_params = None else: