diff --git a/catalyst/examples/buy_the_dip_live.py b/catalyst/examples/buy_the_dip_live.py index a769df27..5ecf87e0 100644 --- a/catalyst/examples/buy_the_dip_live.py +++ b/catalyst/examples/buy_the_dip_live.py @@ -8,8 +8,7 @@ from catalyst.api import ( get_open_orders, ) from catalyst.errors import ZiplineError -import matplotlib.pyplot as plt -import pyfolio as pf +import talib algo_namespace = 'buy_the_dip_live' log = Logger(algo_namespace) @@ -17,12 +16,12 @@ log = Logger(algo_namespace) def initialize(context): log.info('initializing algo') - context.ASSET_NAME = 'EOS_USD' - context.TICK_SIZE = 1000.0 + context.ASSET_NAME = 'IOT_USD' context.asset = symbol(context.ASSET_NAME) - context.TARGET_POSITIONS = 100 - context.BUY_INCREMENT = 1 + context.TARGET_POSITIONS = 200 + context.PROFIT_TARGET = 0.1 + context.SLIPPAGE_ALLOWED = 0.02 context.retry_check_open_orders = 2 context.retry_update_portfolio = 2 @@ -32,28 +31,22 @@ def initialize(context): def _handle_data(context, data): - # price_history = data.history(symbol('iot_usd'), - # fields='price', - # bar_count=20, - # frequency='1d' - # ) - # ohlc = data.history([context.asset, symbol('iot_usd')], - # fields='price', - # bar_count=20, - # frequency='1d' - # ) - 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' - # ) + prices = data.history( + context.asset, + fields='price', + bar_count=20, + frequency='15m' + ) + rsi = talib.RSI(prices.values, timeperiod=14)[-1] + log.info('got rsi: {}'.format(rsi)) - hist_price = ohlc['price'] + # Buying more when RSI is low, this should lower our cost basis + if rsi <= 40: + buy_increment = 2 + elif rsi <= 30: + buy_increment = 5 + else: + buy_increment = 1 cash = context.portfolio.cash log.info('base currency available: {cash}'.format(cash=cash)) @@ -66,7 +59,7 @@ def _handle_data(context, data): log.info('skipping bar until all open orders execute') return - if price * context.BUY_INCREMENT > cash: + if price * buy_increment > cash: log.info('not enough base currency to consider buying') return @@ -83,12 +76,13 @@ def _handle_data(context, data): ) if price < cost_basis: is_buy = True - elif price > cost_basis * 1.1: - log.info('price higher than cost basis, taking profit') + elif price > cost_basis * (1 + context.PROFIT_TARGET) or rsi > 70: + profit = (price * position.amount) - (cost_basis * position.amount) + log.info('closing position, taking profit: {}'.format(profit)) order_target_percent( asset=context.asset, target=0, - limit_price=price * 0.95, + limit_price=price * (1 - context.SLIPPAGE_ALLOWED), ) else: log.info('no buy or sell opportunity found') @@ -104,8 +98,8 @@ def _handle_data(context, data): ) order( asset=context.asset, - amount=context.BUY_INCREMENT, - limit_price=price * 1.1 + amount=buy_increment, + limit_price=price * (1 + context.SLIPPAGE_ALLOWED) ) record( @@ -134,17 +128,7 @@ def handle_data(context, data): 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() - returns, positions, transactions, gross_lev = \ - pf.utils.extract_rets_pos_txn_from_zipline(stats) - + log.info('the full stats:\n{}'.format(stats)) pass diff --git a/catalyst/exchange/algorithm_exchange.py b/catalyst/exchange/algorithm_exchange.py index ed9f5b0d..a9e9bbc0 100644 --- a/catalyst/exchange/algorithm_exchange.py +++ b/catalyst/exchange/algorithm_exchange.py @@ -226,8 +226,12 @@ class ExchangeTradingAlgorithm(TradingAlgorithm): shorts_count=pos_stats.shorts_count, ) + # Merging cumulative risk stats.update(tracker.cumulative_risk_metrics.to_dict()) + # Merging latest recorded variables + stats.update(self.recorded_vars) + stats['positions'] = period.position_tracker.get_positions_list() # we want the key to be absent, not just empty diff --git a/catalyst/exchange/bitfinex.py b/catalyst/exchange/bitfinex.py index ffcd888e..9a8aad48 100644 --- a/catalyst/exchange/bitfinex.py +++ b/catalyst/exchange/bitfinex.py @@ -1,3 +1,4 @@ +import re import pytz import six import base64 @@ -279,17 +280,43 @@ class Bitfinex(Exchange): def get_candles(self, data_frequency, assets, end_dt=None, bar_count=None, limit=None): + """ + Retrieve OHLVC candles from Bitfinex - # TODO: support all available frequencies - start_dt = None - if data_frequency == 'minute' or data_frequency == '1m': + :param data_frequency: + :param assets: + :param end_dt: + :param bar_count: + :param limit: + :return: + + Available Frequencies + --------------------- + '1m', '5m', '15m', '30m', '1h', '3h', '6h', '12h', '1D', '7D', '14D', + '1M' + """ + freq_match = re.match(r'([0-9].*)(m|h|d)', data_frequency, re.M | re.I) + if freq_match: + number = int(freq_match.group(1)) + unit = freq_match.group(2) + + if unit == 'd': + converted_unit = 'D' + else: + converted_unit = unit + + frequency = '{}{}'.format(number, converted_unit) + allowed_frequencies = ['1m', '5m', '15m', '30m', '1h', '3h', '6h', + '12h', '1D', '7D', '14D', '1M'] + + if frequency not in allowed_frequencies: + raise InvalidHistoryFrequencyError( + frequency=data_frequency + ) + elif data_frequency == 'minute': frequency = '1m' - if bar_count and end_dt: - start_dt = end_dt - timedelta(minutes=bar_count) - elif data_frequency == 'daily' or data_frequency == '1d': + elif data_frequency == 'daily': frequency = '1D' - if bar_count and end_dt: - start_dt = end_dt - timedelta(days=bar_count) else: raise InvalidHistoryFrequencyError( frequency=data_frequency @@ -306,28 +333,26 @@ class Bitfinex(Exchange): symbol=symbol ) - if start_dt and end_dt: + if bar_count: is_list = True - url += '/hist?start={start}&end={end}'.format( - start=time.mktime(start_dt.timetuple()) * 1000, - end=time.mktime(end_dt.timetuple()) * 1000, - ) + url += '/hist?limit={}'.format(int(bar_count)) else: is_list = False url += '/last' try: response = requests.get(url) - candles = response.json() except Exception as e: raise ExchangeRequestError(error=e) - if 'message' in candles: + if 'error' in response.content: raise ExchangeRequestError( error='Unable to retrieve candles: {}'.format( - candles['message']) + response.content) ) + candles = response.json() + def ohlc_from_candle(candle): return dict( open=candle[1], @@ -342,7 +367,8 @@ class Bitfinex(Exchange): if is_list: ohlc_bars = [] - for candle in candles: + # We can to list candles from old to new + for candle in reversed(candles): ohlc = ohlc_from_candle(candle) ohlc_bars.append(ohlc)