From 01b02ccd8417c21a244807de2260d09a7d3c4f87 Mon Sep 17 00:00:00 2001 From: Maciek Date: Sat, 18 Nov 2017 13:40:50 +1100 Subject: [PATCH 01/52] Python 3 support #catalyst/curate/poloniex.py: Change `print url` to `print(url)` --- catalyst/curate/poloniex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/catalyst/curate/poloniex.py b/catalyst/curate/poloniex.py index 572cd777..2e51e4fb 100644 --- a/catalyst/curate/poloniex.py +++ b/catalyst/curate/poloniex.py @@ -129,7 +129,7 @@ class PoloniexCurator(object): start = str(newstart), end = str(end) ) - print url + print(url) attempts = 0 success = 0 From 2fdb4dd0bd0645dfd611b8490759a443b146edbf Mon Sep 17 00:00:00 2001 From: Maciek Date: Mon, 27 Nov 2017 20:59:47 +1100 Subject: [PATCH 02/52] Decode poloniex_api.query with utf-8 --- catalyst/exchange/poloniex/poloniex_api.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/catalyst/exchange/poloniex/poloniex_api.py b/catalyst/exchange/poloniex/poloniex_api.py index 894ba08b..ce0831bc 100644 --- a/catalyst/exchange/poloniex/poloniex_api.py +++ b/catalyst/exchange/poloniex/poloniex_api.py @@ -107,8 +107,9 @@ class Poloniex_api(object): data=post_data, headers=headers, ) - return json.loads( - urlopen(req, context=ssl._create_unverified_context()).read()) + resource = urlopen(req, context=ssl._create_unverified_context()) + content = resource.read().decode('utf-8') + return json.loads(content) def returnticker(self): return self.query('returnTicker', {}) From 606148e19c2a14a31653acc34d7099c9a50dae3b Mon Sep 17 00:00:00 2001 From: fredfortier Date: Tue, 28 Nov 2017 13:42:58 -0500 Subject: [PATCH 03/52] DOC: Integrating with ccxt --- catalyst/exchange/ccxt/__init__.py | 0 catalyst/exchange/ccxt/ccxt_exchange.py | 121 ++++++++++++++++++++++++ etc/requirements.txt | 3 + tests/exchange/test_ccxt.py | 101 ++++++++++++++++++++ 4 files changed, 225 insertions(+) create mode 100644 catalyst/exchange/ccxt/__init__.py create mode 100644 catalyst/exchange/ccxt/ccxt_exchange.py create mode 100644 tests/exchange/test_ccxt.py diff --git a/catalyst/exchange/ccxt/__init__.py b/catalyst/exchange/ccxt/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py new file mode 100644 index 00000000..86673a1c --- /dev/null +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -0,0 +1,121 @@ +import re +from collections import defaultdict + +import ccxt +import pandas as pd +from logbook import Logger + +from catalyst.constants import LOG_LEVEL +from catalyst.exchange.exchange import Exchange +from catalyst.exchange.exchange_bundle import ExchangeBundle +from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError + +log = Logger('CCXT', level=LOG_LEVEL) + + +class CCXT(Exchange): + def __init__(self, exchange_name, key, secret, base_currency, + portfolio=None): + log.debug('available exchanges:\n{}'.format(ccxt.exchanges)) + self.api = ccxt.poloniex({ + 'apiKey': key, + 'secret': secret, + }) + markets = self.api.load_markets() + log.debug('the markets:\n{}'.format(markets)) + + self.name = exchange_name + self.assets = {} + self.load_assets() + self.base_currency = base_currency + self._portfolio = portfolio + self.transactions = defaultdict(list) + + self.num_candles_limit = 2000 + self.max_requests_per_minute = 60 + self.request_cpt = dict() + + self.bundle = ExchangeBundle(self.name) + + def account(self): + return None + + def time_skew(self): + return None + + def get_symbol(self, asset): + parts = asset.symbol.split('_') + return '{}/{}'.format(parts[0].upper(), parts[1].upper()) + + def get_timeframe(self, freq): + freq_match = re.match(r'([0-9].*)?(m|M|d|D|h|H|T)', freq, re.M | re.I) + if freq_match: + candle_size = int(freq_match.group(1)) \ + if freq_match.group(1) else 1 + + unit = freq_match.group(2) + + else: + raise InvalidHistoryFrequencyError(frequency=freq) + + if unit.lower() == 'd': + timeframe = '{}d'.format(candle_size) + + elif unit.lower() == 'm' or unit == 'T': + timeframe = '{}m'.format(candle_size) + + elif unit.lower() == 'h' or unit == 'T': + timeframe = '{}h'.format(candle_size) + + return timeframe + + def get_candles(self, freq, assets, bar_count=None, start_dt=None, + end_dt=None): + symbols = self.get_symbols(assets) + timeframe = self.get_timeframe(freq) + delta = start_dt - pd.to_datetime('1970-1-1', utc=True) + ms = int(delta.total_seconds()) * 1000 + + ohlcvs = self.api.fetch_ohlcv( + symbol=symbols[0], + timeframe=timeframe, + since=ms, + limit=bar_count, + params={} + ) + + candles = [] + for ohlcv in ohlcvs: + candles.append(dict( + last_traded=pd.to_datetime(ohlcv[0], unit='ms', utc=True), + open=ohlcv[1], + high=ohlcv[2], + low=ohlcv[3], + close=ohlcv[4], + volume=ohlcv[5] + )) + return candles + + def get_balances(self): + return None + + def create_order(self, asset, amount, is_buy, style): + return None + + def get_open_orders(self, asset): + return None + + def get_order(self, order_id): + return None + + def cancel_order(self, order_param): + return None + + def tickers(self, assets): + return None + + def get_account(self): + return None + + def get_orderbook(self, asset, order_type, limit): + return None diff --git a/etc/requirements.txt b/etc/requirements.txt index aaa129ca..dee52a34 100644 --- a/etc/requirements.txt +++ b/etc/requirements.txt @@ -80,3 +80,6 @@ empyrical==0.2.1 tables==3.3.0 +#Catalyst dependencies +ccxt==1.10.251 + diff --git a/tests/exchange/test_ccxt.py b/tests/exchange/test_ccxt.py new file mode 100644 index 00000000..7fd0dcff --- /dev/null +++ b/tests/exchange/test_ccxt.py @@ -0,0 +1,101 @@ +import os +import tempfile + +import pandas as pd +from catalyst.exchange.ccxt.ccxt_exchange import CCXT +from catalyst.finance.order import Order +from base import BaseExchangeTestCase +from logbook import Logger +from catalyst.exchange.exchange_utils import get_exchange_auth +from catalyst.utils.paths import ensure_directory + +log = Logger('test_ccxt') + + +class TestCCXT(BaseExchangeTestCase): + @classmethod + def setup(self): + exchange_name = 'poloniex' + auth = get_exchange_auth(exchange_name) + self.exchange = CCXT( + exchange_name=exchange_name, + key=auth['key'], + secret=auth['secret'], + base_currency=None, + portfolio=None + ) + + def test_order(self): + log.info('creating order') + asset = self.exchange.get_asset('neo_btc') + order_id = self.exchange.order( + asset=asset, + limit_price=0.0005, + amount=1, + ) + log.info('order created {}'.format(order_id)) + assert order_id is not None + pass + + def test_open_orders(self): + log.info('retrieving open orders') + asset = self.exchange.get_asset('neo_btc') + orders = self.exchange.get_open_orders(asset) + pass + + def test_get_order(self): + log.info('retrieving order') + order = self.exchange.get_order( + u'2c584020-9caf-4af5-bde0-332c0bba17e2') + assert isinstance(order, Order) + pass + + def test_cancel_order(self, ): + log.info('cancel order') + self.exchange.cancel_order(u'dc7bcca2-5219-4145-8848-8a593d2a72f9') + pass + + def test_get_candles(self): + log.info('retrieving candles') + candles = self.exchange.get_candles( + freq='5T', + assets=[self.exchange.get_asset('eth_btc')], + bar_count=200, + start_dt=pd.to_datetime('2017-01-01', utc=True) + ) + + df = pd.DataFrame(candles) + df.set_index('last_traded', drop=True, inplace=True) + + folder = os.path.join( + tempfile.gettempdir(), 'catalyst', self.exchange.name, 'eth_btc' + ) + ensure_directory(folder) + + path = os.path.join(folder, 'output.csv') + df.to_csv(path) + pass + + def test_tickers(self): + log.info('retrieving tickers') + tickers = self.exchange.tickers([ + self.exchange.get_asset('eth_btc'), + self.exchange.get_asset('etc_btc') + ]) + assert len(tickers) == 2 + pass + + def test_get_balances(self): + log.info('testing wallet balances') + balances = self.exchange.get_balances() + pass + + def test_get_account(self): + log.info('testing account data') + pass + + def test_orderbook(self): + log.info('testing order book for bittrex') + asset = self.exchange.get_asset('eth_btc') + orderbook = self.exchange.get_orderbook(asset) + pass From daeccaed364737dece28e48edd9f502ee650da8c Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Tue, 28 Nov 2017 16:44:13 -0700 Subject: [PATCH 04/52] DOC: video - live trading --- docs/source/videos.rst | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/source/videos.rst b/docs/source/videos.rst index d93c09c5..90b53f19 100644 --- a/docs/source/videos.rst +++ b/docs/source/videos.rst @@ -32,7 +32,9 @@ Where things don't: Backtesting a Strategy ---------------------- -This algorithm is based on a simple momentum strategy. When the cryptoasset +This is the first video of a two-part series on using Catalyst for algorithmic +trading. This video implements a simple momentum strategy based on +`mean reversion `_: when the cryptoasset goes up quickly, we’re going to buy; when it goes down quickly, we’re going to sell. Hopefully, we’ll ride the waves. @@ -40,3 +42,17 @@ sell. Hopefully, we’ll ride the waves. +| +| +Live Trading a Strategy +----------------------- + +This is the second part of the two-part series on using Catalyst for algorithmic +trading. Having backtested `our strategy `_ +in the previous video, we now take it to trade live against the Bittrex exchange. + +.. raw:: html + + +| +| \ No newline at end of file From c8eaa11f801cbd9a07adcd1da13c6517b818fa14 Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Wed, 29 Nov 2017 09:37:46 -0700 Subject: [PATCH 05/52] DOC: added portfolio_optimization to documented examples --- docs/source/example-algos.rst | 152 ++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/docs/source/example-algos.rst b/docs/source/example-algos.rst index 002dc46b..550d8b58 100644 --- a/docs/source/example-algos.rst +++ b/docs/source/example-algos.rst @@ -31,6 +31,13 @@ Overview `two-part video tutorial `_ to show how to get started in backtesting and live trading with Catalyst. +- :ref:`Portfolio Optimization `: Use this code to + execute a portfolio optimization model. This strategy will select the + portfolio with the maximum Sharpe Ratio. The parameters are set to use 180 + days of historical data and rebalance every 30 days. This code was used in + writting the following article: + `Markowitz Portfolio Optimization for Cryptocurrencies `_. + .. _buy_btc_simple: @@ -746,4 +753,149 @@ implemented after the video was recorded, which executes the orders at slighlty different prices, but resulting in significant changes in performance of our strategy. +.. _portfolio_optimization: + +Portfolio Optimization +~~~~~~~~~~~~~~~~~~~~~~ + +Use this code to execute a portfolio optimization model. This strategy will +select the portfolio with the maximum Sharpe Ratio. The parameters are set to +use 180 days of historical data and rebalance every 30 days. This code was used +in writting the following article: +`Markowitz Portfolio Optimization for Cryptocurrencies `_. + +.. code-block:: python + + ''' + You can run this code using the Python interpreter: + + $ python portfolio_optimization.py + ''' + + from __future__ import division + import os + import pytz + import numpy as np + import pandas as pd + from scipy.optimize import minimize + import matplotlib.pyplot as plt + from datetime import datetime + + from catalyst.api import record, symbol, symbols, order_target_percent + from catalyst.utils.run_algo import run_algorithm + + np.set_printoptions(threshold='nan', suppress=True) + + + def initialize(context): + # Portfolio assets list + context.assets = symbols('btc_usdt', 'eth_usdt', 'ltc_usdt', 'dash_usdt', + 'xmr_usdt') + context.nassets = len(context.assets) + # Set the time window that will be used to compute expected return + # and asset correlations + context.window = 180 + # Set the number of days between each portfolio rebalancing + context.rebalance_period = 30 + context.i = 0 + + + def handle_data(context, data): + # Only rebalance at the beggining of the algorithm execution and + # every multiple of the rebalance period + if context.i == 0 or context.i%context.rebalance_period == 0: + n = context.window + prices = data.history(context.assets, fields='price', + bar_count=n+1, frequency='1d') + pr = np.asmatrix(prices) + t_prices = prices.iloc[1:n+1] + t_val = t_prices.values + tminus_prices = prices.iloc[0:n] + tminus_val = tminus_prices.values + # Compute daily returns (r) + r = np.asmatrix(t_val/tminus_val-1) + # Compute the expected returns of each asset with the average + # daily return for the selected time window + m = np.asmatrix(np.mean(r, axis=0)) + # ### + stds = np.std(r, axis=0) + # Compute excess returns matrix (xr) + xr = r - m + # Matrix algebra to get variance-covariance matrix + cov_m = np.dot(np.transpose(xr),xr)/n + # Compute asset correlation matrix (informative only) + corr_m = cov_m/np.dot(np.transpose(stds),stds) + + # Define portfolio optimization parameters + n_portfolios = 50000 + results_array = np.zeros((3+context.nassets,n_portfolios)) + for p in xrange(n_portfolios): + weights = np.random.random(context.nassets) + weights /= np.sum(weights) + w = np.asmatrix(weights) + p_r = np.sum(np.dot(w,np.transpose(m)))*365 + p_std = np.sqrt(np.dot(np.dot(w,cov_m),np.transpose(w)))*np.sqrt(365) + + #store results in results array + results_array[0,p] = p_r + results_array[1,p] = p_std + #store Sharpe Ratio (return / volatility) - risk free rate element + #excluded for simplicity + results_array[2,p] = results_array[0,p] / results_array[1,p] + i = 0 + for iw in weights: + results_array[3+i,p] = weights[i] + i += 1 + + #convert results array to Pandas DataFrame + results_frame = pd.DataFrame(np.transpose(results_array), + columns=['r','stdev','sharpe']+context.assets) + #locate position of portfolio with highest Sharpe Ratio + max_sharpe_port = results_frame.iloc[results_frame['sharpe'].idxmax()] + #locate positon of portfolio with minimum standard deviation + min_vol_port = results_frame.iloc[results_frame['stdev'].idxmin()] + + #order optimal weights for each asset + for asset in context.assets: + if data.can_trade(asset): + order_target_percent(asset, max_sharpe_port[asset]) + + #create scatter plot coloured by Sharpe Ratio + plt.scatter(results_frame.stdev,results_frame.r,c=results_frame.sharpe,cmap='RdYlGn') + plt.xlabel('Volatility') + plt.ylabel('Returns') + plt.colorbar() + #plot red star to highlight position of portfolio with highest Sharpe Ratio + plt.scatter(max_sharpe_port[1],max_sharpe_port[0],marker='o',color='b',s=200) + #plot green star to highlight position of minimum variance portfolio + plt.show() + print(max_sharpe_port) + record(pr=pr,r=r, m=m, stds=stds ,max_sharpe_port=max_sharpe_port, corr_m=corr_m) + context.i += 1 + + + def analyze(context=None, results=None): + # Form DataFrame with selected data + data = results[['pr','r','m','stds','max_sharpe_port','corr_m','portfolio_value']] + + # Save results in CSV file + filename = os.path.splitext(os.path.basename(__file__))[0] + data.to_csv(filename + '.csv') + + + # Bitcoin data is available from 2015-3-2. Dates vary for other tokens. + start = datetime(2017, 1, 1, 0, 0, 0, 0, pytz.utc) + end = datetime(2017, 8, 16, 0, 0, 0, 0, pytz.utc) + results = run_algorithm(initialize=initialize, + handle_data=handle_data, + analyze=analyze, + start=start, + end=end, + exchange_name='poloniex', + capital_base=100000, ) + +.. image:: https://cdn-images-1.medium.com/max/1600/0*EjjiKZHlYF3sn7yQ. + :align: center + + From 4eb8a6eb0f9f59632bdfafa1aed806aa0ca03633 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Wed, 29 Nov 2017 22:31:44 -0500 Subject: [PATCH 06/52] BLD: populating assets with help from CCXT --- catalyst/assets/_assets.pyx | 36 +++++++++++--- catalyst/examples/mean_reversion_simple.py | 8 ++-- catalyst/exchange/ccxt/ccxt_exchange.py | 56 +++++++++++++++++++++- catalyst/exchange/exchange_utils.py | 19 ++++++++ tests/exchange/test_ccxt.py | 3 ++ 5 files changed, 110 insertions(+), 12 deletions(-) diff --git a/catalyst/assets/_assets.pyx b/catalyst/assets/_assets.pyx index 3537493f..e78e3af1 100644 --- a/catalyst/assets/_assets.pyx +++ b/catalyst/assets/_assets.pyx @@ -401,6 +401,10 @@ cdef class TradingPair(Asset): cdef readonly object end_daily cdef readonly object end_minute cdef readonly object exchange_symbol + cdef readonly float maker + cdef readonly float taker + cdef readonly int trading_state + cdef readonly object data_source _kwargnames = frozenset({ 'sid', @@ -418,7 +422,11 @@ cdef class TradingPair(Asset): 'end_daily', 'end_minute', 'exchange_symbol', - 'min_trade_size' + 'min_trade_size', + 'maker', + 'taker', + 'trading_state', + 'data_source' }) def __init__(self, object symbol, @@ -434,10 +442,14 @@ cdef class TradingPair(Asset): object first_traded=None, object auto_close_date=None, object exchange_full=None, - object min_trade_size=None): + float min_trade_size=0.000001, + float maker=0.0015, + float taker=0.0025, + int trading_state=0, + object data_source='catalyst'): """ Replicates the Asset constructor with some built-in conventions - and a new 'leverage' attribute. + and adds properties for leverage and fees. Symbol ------ @@ -469,8 +481,6 @@ cdef class TradingPair(Asset): highest volume and market cap generally benefit from high leverage. New currencies from ICO generally cannot be leveraged. - The leverage value is either None or and integer. - Leverage allows you to open a larger position with a smaller amount of funds. For example, if you open a $5,000 position in BTC/USD with 5:1 leverage, only one-fifth of this amount, or $1000, will be @@ -480,6 +490,11 @@ cdef class TradingPair(Asset): the position. If you open with 1:1 leverage, $5,000 of your balance will be tied to the position. + Fees + ---- + Exchanges generally charge a taker (taking from the order book) or + maker (adding to the order book) fee. + :param symbol: :param exchange: :param start_date: @@ -494,6 +509,9 @@ cdef class TradingPair(Asset): :param auto_close_date: :param exchange_full: :param min_trade_size: + :param maker: + :param taker: + :param data_source """ symbol = symbol.lower() @@ -527,13 +545,17 @@ cdef class TradingPair(Asset): first_traded=first_traded, auto_close_date=auto_close_date, exchange_full=exchange_full, - min_trade_size=min_trade_size + min_trade_size=min_trade_size, ) + self.maker = maker + self.taker = taker self.leverage = leverage self.end_daily = end_daily self.end_minute = end_minute self.exchange_symbol = exchange_symbol + self.trading_state = trading_state + self.data_source = data_source def __repr__(self): return 'Trading Pair {symbol}({sid}) Exchange: {exchange}, ' \ @@ -579,7 +601,7 @@ cdef class TradingPair(Asset): boolean: whether the asset's exchange is open at the given minute. """ #TODO: consider implementing to spot holds - return True + return self.trading_state > 0 cpdef __reduce__(self): """ diff --git a/catalyst/examples/mean_reversion_simple.py b/catalyst/examples/mean_reversion_simple.py index 6d4922e1..1927f746 100644 --- a/catalyst/examples/mean_reversion_simple.py +++ b/catalyst/examples/mean_reversion_simple.py @@ -36,8 +36,8 @@ def initialize(context): context.base_price = None context.current_day = None - context.RSI_OVERSOLD = 50 - context.RSI_OVERBOUGHT = 80 + context.RSI_OVERSOLD = 25 + context.RSI_OVERBOUGHT = 82 context.CANDLE_SIZE = '5T' context.start_time = time.time() @@ -247,14 +247,14 @@ if __name__ == '__main__': out = os.path.join(folder, '{}.p'.format(timestr)) # catalyst run -f catalyst/examples/mean_reversion_simple.py -x poloniex -s 2017-10-1 -e 2017-11-10 -c usdt -n mean-reversion --data-frequency minute --capital-base 10000 run_algorithm( - capital_base=10000, + capital_base=0.5, data_frequency='minute', initialize=initialize, handle_data=handle_data, analyze=analyze, exchange_name='bitfinex', algo_namespace=NAMESPACE, - base_currency='usd', + base_currency='eth', start=pd.to_datetime('2017-10-01', utc=True), end=pd.to_datetime('2017-11-10', utc=True), output=out diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py index 86673a1c..00dd2970 100644 --- a/catalyst/exchange/ccxt/ccxt_exchange.py +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -3,12 +3,15 @@ from collections import defaultdict import ccxt import pandas as pd +from catalyst.assets._assets import TradingPair from logbook import Logger from catalyst.constants import LOG_LEVEL from catalyst.exchange.exchange import Exchange from catalyst.exchange.exchange_bundle import ExchangeBundle -from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError +from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \ + ExchangeSymbolsNotFound +from catalyst.exchange.exchange_utils import mixin_market_params log = Logger('CCXT', level=LOG_LEVEL) @@ -96,6 +99,57 @@ class CCXT(Exchange): )) return candles + def load_assets(self, is_local=False): + markets = self.api.fetch_markets() + try: + symbol_map = self.fetch_symbol_map(is_local) + except ExchangeSymbolsNotFound: + return None + + data_source = 'local' if is_local else 'catalyst' + for market in markets: + asset = symbol_map[market['id']] \ + if market['id'] in markets else None + + params = dict(exchange=self.name, data_source=data_source) + mixin_market_params(params, market) + + if asset: + params['symbol'] = asset['symbol'] + + params['start_date'] = pd.to_datetime( + asset['start_date'], utc=True + ) if 'start_date' in asset else None + + params['end_date'] = pd.to_datetime( + asset['end_date'], utc=True + ) if 'end_date' in asset else None + + params['leverage'] = asset['leverage'] \ + if 'leverage' in asset else 1.0 + + params['asset_name'] = asset['asset_name'] \ + if 'asset_name' in asset else None + + params['end_daily'] = pd.to_datetime( + asset['end_daily'], utc=True + ) if 'end_daily' in asset and asset['end_daily'] != 'N/A' \ + else None + + params['end_minute'] = pd.to_datetime( + asset['end_minute'], utc=True + ) if 'end_minute' in asset and asset['end_minute'] != 'N/A' \ + else None + + else: + params['symbol'] = market['id'] + + trading_pair = TradingPair(**params) + if is_local: + self.local_assets[market['id']] = trading_pair + else: + self.assets[market['id']] = trading_pair + def get_balances(self): return None diff --git a/catalyst/exchange/exchange_utils.py b/catalyst/exchange/exchange_utils.py index 678d9945..83dfae41 100644 --- a/catalyst/exchange/exchange_utils.py +++ b/catalyst/exchange/exchange_utils.py @@ -571,3 +571,22 @@ def resample_history_df(df, freq, field): resampled_df = df.resample(freq).agg(agg) return resampled_df + + +def mixin_market_params(params, market): + """ + Applies a CCXT market dict to parameters of TradingPair init. + + Parameters + ---------- + params: dict[Object] + market: dict[Object] + + Returns + ------- + + """ + params['min_trade_size'] = market['lot'] + params['maker'] = market['maker'] + params['taker'] = market['taker'] + params['trading_state'] = 1 if int(market['info']['isFrozen']) == 0 else 0 diff --git a/tests/exchange/test_ccxt.py b/tests/exchange/test_ccxt.py index 7fd0dcff..f920fa39 100644 --- a/tests/exchange/test_ccxt.py +++ b/tests/exchange/test_ccxt.py @@ -99,3 +99,6 @@ class TestCCXT(BaseExchangeTestCase): asset = self.exchange.get_asset('eth_btc') orderbook = self.exchange.get_orderbook(asset) pass + + def test_get_fees(self): + pass From 5660247da2b4043e56a337a559bc5a3dcb8e45eb Mon Sep 17 00:00:00 2001 From: fredfortier Date: Thu, 30 Nov 2017 17:08:13 -0500 Subject: [PATCH 07/52] BLD: tested all public APIs with CCXT --- catalyst/assets/_assets.pyx | 14 +-- catalyst/exchange/ccxt/ccxt_exchange.py | 134 +++++++++++++++++++----- catalyst/exchange/exchange.py | 15 +-- catalyst/exchange/exchange_errors.py | 4 + catalyst/exchange/exchange_utils.py | 29 ++++- tests/exchange/test_ccxt.py | 4 +- 6 files changed, 156 insertions(+), 44 deletions(-) diff --git a/catalyst/assets/_assets.pyx b/catalyst/assets/_assets.pyx index e78e3af1..feb41710 100644 --- a/catalyst/assets/_assets.pyx +++ b/catalyst/assets/_assets.pyx @@ -396,7 +396,7 @@ cdef class Future(Asset): cdef class TradingPair(Asset): cdef readonly float leverage - cdef readonly object market_currency + cdef readonly object quote_currency cdef readonly object base_currency cdef readonly object end_daily cdef readonly object end_minute @@ -417,7 +417,7 @@ cdef class TradingPair(Asset): 'exchange', 'exchange_full', 'leverage', - 'market_currency', + 'quote_currency', 'base_currency', 'end_daily', 'end_minute', @@ -442,7 +442,7 @@ cdef class TradingPair(Asset): object first_traded=None, object auto_close_date=None, object exchange_full=None, - float min_trade_size=0.000001, + float min_trade_size=0.0001, float maker=0.0015, float taker=0.0025, int trading_state=0, @@ -516,7 +516,7 @@ cdef class TradingPair(Asset): symbol = symbol.lower() try: - self.market_currency, self.base_currency = symbol.split('_') + self.base_currency,self.quote_currency = symbol.split('_') except Exception as e: raise InvalidSymbolError(symbol=symbol, error=e) @@ -530,7 +530,7 @@ cdef class TradingPair(Asset): asset_name = ' / '.join(symbol.split('_')).upper() if start_date is None: - start_date = pd.Timestamp.utcnow() + start_date = pd.to_datetime('2009-1-1', utc=True) if end_date is None: end_date = pd.Timestamp.utcnow() + timedelta(days=365) @@ -560,8 +560,8 @@ cdef class TradingPair(Asset): def __repr__(self): return 'Trading Pair {symbol}({sid}) Exchange: {exchange}, ' \ 'Introduced On: {start_date}, ' \ - 'Market Currency: {market_currency}, ' \ 'Base Currency: {base_currency}, ' \ + 'Quote Currency: {quote_currency}, ' \ 'Exchange Leverage: {leverage}, ' \ 'Minimum Trade Size: {min_trade_size} ' \ 'Last daily ingestion: {end_daily} ' \ @@ -570,7 +570,7 @@ cdef class TradingPair(Asset): sid=self.sid, exchange=self.exchange, start_date=self.start_date, - market_currency=self.market_currency, + quote_currency=self.quote_currency, base_currency=self.base_currency, leverage=self.leverage, min_trade_size=self.min_trade_size, diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py index 00dd2970..58be745c 100644 --- a/catalyst/exchange/ccxt/ccxt_exchange.py +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -11,7 +11,8 @@ from catalyst.exchange.exchange import Exchange from catalyst.exchange.exchange_bundle import ExchangeBundle from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \ ExchangeSymbolsNotFound -from catalyst.exchange.exchange_utils import mixin_market_params +from catalyst.exchange.exchange_utils import mixin_market_params, \ + from_ms_timestamp log = Logger('CCXT', level=LOG_LEVEL) @@ -19,17 +20,28 @@ log = Logger('CCXT', level=LOG_LEVEL) class CCXT(Exchange): def __init__(self, exchange_name, key, secret, base_currency, portfolio=None): - log.debug('available exchanges:\n{}'.format(ccxt.exchanges)) - self.api = ccxt.poloniex({ - 'apiKey': key, - 'secret': secret, - }) + log.debug( + 'finding {} in CCXT exchanges:\n{}'.format( + exchange_name, ccxt.exchanges + ) + ) + try: + exchange_attr = getattr(ccxt, exchange_name) + self.api = exchange_attr({ + 'apiKey': key, + 'secret': secret, + }) + except Exception: + raise ValueError('exchange not in CCXT') + markets = self.api.load_markets() log.debug('the markets:\n{}'.format(markets)) self.name = exchange_name - self.assets = {} + + self.assets = dict() self.load_assets() + self.base_currency = base_currency self._portfolio = portfolio self.transactions = defaultdict(list) @@ -50,6 +62,10 @@ class CCXT(Exchange): parts = asset.symbol.split('_') return '{}/{}'.format(parts[0].upper(), parts[1].upper()) + def get_catalyst_symbol(self, market): + parts = market['symbol'].split('/') + return '{}_{}'.format(parts[0].lower(), parts[1].lower()) + def get_timeframe(self, freq): freq_match = re.match(r'([0-9].*)?(m|M|d|D|h|H|T)', freq, re.M | re.I) if freq_match: @@ -99,22 +115,49 @@ class CCXT(Exchange): )) return candles - def load_assets(self, is_local=False): - markets = self.api.fetch_markets() + def _fetch_symbol_map(self, is_local): try: - symbol_map = self.fetch_symbol_map(is_local) + return self.fetch_symbol_map(is_local) except ExchangeSymbolsNotFound: return None - data_source = 'local' if is_local else 'catalyst' + def _fetch_asset(self, market_id, is_local=False): + symbol_map = self._fetch_symbol_map(is_local) + if symbol_map is not None: + assets_lower = {k.lower(): v for k, v in symbol_map.items()} + key = market_id.lower() + + asset = assets_lower[key] if key in assets_lower else None + if asset is not None: + return asset, is_local + + elif not is_local: + return self._fetch_asset(market_id, True) + + else: + return None, is_local + + elif not is_local: + return self._fetch_asset(market_id, True) + + else: + return None, is_local + + def load_assets(self): + markets = self.api.fetch_markets() + for market in markets: - asset = symbol_map[market['id']] \ - if market['id'] in markets else None + asset, is_local = self._fetch_asset(market['id']) + data_source = 'local' if is_local else 'catalyst' - params = dict(exchange=self.name, data_source=data_source) - mixin_market_params(params, market) + params = dict( + exchange=self.name, + data_source=data_source, + exchange_symbol=market['id'], + ) + mixin_market_params(self.name, params, market) - if asset: + if asset is not None: params['symbol'] = asset['symbol'] params['start_date'] = pd.to_datetime( @@ -142,13 +185,10 @@ class CCXT(Exchange): else None else: - params['symbol'] = market['id'] + params['symbol'] = self.get_catalyst_symbol(market) trading_pair = TradingPair(**params) - if is_local: - self.local_assets[market['id']] = trading_pair - else: - self.assets[market['id']] = trading_pair + self.assets[market['id']] = trading_pair def get_balances(self): return None @@ -166,10 +206,56 @@ class CCXT(Exchange): return None def tickers(self, assets): - return None + """ + Retrieve current tick data for the given assets + + Parameters + ---------- + assets: list[TradingPair] + + Returns + ------- + list[dict[str, float] + + """ + tickers = dict() + for asset in assets: + ccxt_symbol = self.get_symbol(asset) + ticker = self.api.fetch_ticker(ccxt_symbol) + + ticker['last_traded'] = from_ms_timestamp(ticker['timestamp']) + + # Using the volume represented in the base currency + ticker['volume'] = ticker['baseVolume'] \ + if 'baseVolume' in ticker else 0 + + tickers[asset] = ticker + + return tickers def get_account(self): return None - def get_orderbook(self, asset, order_type, limit): - return None + def get_orderbook(self, asset, order_type='all', limit=None): + ccxt_symbol = self.get_symbol(asset) + + params = dict() + if limit is not None: + params['depth'] = limit + + order_book = self.api.fetch_order_book(ccxt_symbol, params) + + order_types = ['bids', 'asks'] if order_type == 'all' else [order_type] + result = dict(last_traded=from_ms_timestamp(order_book['timestamp'])) + for index, order_type in enumerate(order_types): + if limit is not None and index > limit - 1: + break + + result[order_type] = [] + for entry in order_book[order_type]: + result[order_type].append(dict( + rate=float(entry[0]), + quantity=float(entry[1]) + )) + + return result diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index f43424aa..8a69d3fb 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -16,7 +16,7 @@ from catalyst.exchange.exchange_bundle import ExchangeBundle from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \ InvalidOrderStyle, BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \ PricingDataNotLoadedError, \ - NoDataAvailableOnExchange, ExchangeSymbolsNotFound + NoDataAvailableOnExchange, ExchangeSymbolsNotFound, NoValueForField from catalyst.exchange.exchange_execution import ExchangeStopLimitOrder, \ ExchangeLimitOrder, ExchangeStopOrder from catalyst.exchange.exchange_portfolio import ExchangePortfolio @@ -412,12 +412,15 @@ class Exchange: if field not in BASE_FIELDS: raise KeyError('Invalid column: {}'.format(field)) - values = [] - for asset in assets: - value = self.get_single_spot_value(asset, field, data_frequency) - values.append(value) + tickers = self.tickers(assets) + if field == 'close' or field == 'price': + return [t['last'] for t in tickers] - return values + elif field == 'volume': + return [t['volume'] for t in tickers] + + else: + raise NoValueForField(field=field) def get_single_spot_value(self, asset, field, data_frequency): """ diff --git a/catalyst/exchange/exchange_errors.py b/catalyst/exchange/exchange_errors.py index cb4f4d32..5530ccb2 100644 --- a/catalyst/exchange/exchange_errors.py +++ b/catalyst/exchange/exchange_errors.py @@ -240,3 +240,7 @@ class NoDataAvailableOnExchange(ZiplineError): 'Requested data for trading pair {symbol} is not available on exchange {exchange} ' 'in `{data_frequency}` frequency at this time. ' 'Check `http://enigma.co/catalyst/status` for market coverage.').strip() + + +class NoValueForField(ZiplineError): + msg = ('Value not found for field: {field}.').strip() diff --git a/catalyst/exchange/exchange_utils.py b/catalyst/exchange/exchange_utils.py index 83dfae41..ee0ce44e 100644 --- a/catalyst/exchange/exchange_utils.py +++ b/catalyst/exchange/exchange_utils.py @@ -573,7 +573,7 @@ def resample_history_df(df, freq, field): return resampled_df -def mixin_market_params(params, market): +def mixin_market_params(exchange_name, params, market): """ Applies a CCXT market dict to parameters of TradingPair init. @@ -586,7 +586,26 @@ def mixin_market_params(params, market): ------- """ - params['min_trade_size'] = market['lot'] - params['maker'] = market['maker'] - params['taker'] = market['taker'] - params['trading_state'] = 1 if int(market['info']['isFrozen']) == 0 else 0 + # TODO: make this more externalized / configurable + if 'lot' in market: + params['min_trade_size'] = market['lot'] + + if exchange_name == 'bitfinex': + params['maker'] = 0.001 + params['taker'] = 0.002 + + else: + if 'maker' in market: + params['maker'] = market['maker'] + + if 'taker' in market: + params['taker'] = market['taker'] + + info = market['info'] if 'info' in market else None + if info: + if 'minimum_order_size' in info: + params['min_trade_size'] = float(info['minimum_order_size']) + + +def from_ms_timestamp(ms): + return pd.to_datetime(ms, unit='ms', utc=True) diff --git a/tests/exchange/test_ccxt.py b/tests/exchange/test_ccxt.py index f920fa39..f00ea049 100644 --- a/tests/exchange/test_ccxt.py +++ b/tests/exchange/test_ccxt.py @@ -15,7 +15,7 @@ log = Logger('test_ccxt') class TestCCXT(BaseExchangeTestCase): @classmethod def setup(self): - exchange_name = 'poloniex' + exchange_name = 'binance' auth = get_exchange_auth(exchange_name) self.exchange = CCXT( exchange_name=exchange_name, @@ -97,7 +97,7 @@ class TestCCXT(BaseExchangeTestCase): def test_orderbook(self): log.info('testing order book for bittrex') asset = self.exchange.get_asset('eth_btc') - orderbook = self.exchange.get_orderbook(asset) + orderbook = self.exchange.get_orderbook(asset, 'all', limit=10) pass def test_get_fees(self): From b587804e3e4936ad7d747097c13fde38e263be96 Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Thu, 30 Nov 2017 17:11:15 -0700 Subject: [PATCH 08/52] DOC: fix video links to algos --- docs/source/videos.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/videos.rst b/docs/source/videos.rst index 90b53f19..1db8ff28 100644 --- a/docs/source/videos.rst +++ b/docs/source/videos.rst @@ -34,7 +34,7 @@ Backtesting a Strategy This is the first video of a two-part series on using Catalyst for algorithmic trading. This video implements a simple momentum strategy based on -`mean reversion `_: when the cryptoasset +`mean reversion `_: when the cryptoasset goes up quickly, we’re going to buy; when it goes down quickly, we’re going to sell. Hopefully, we’ll ride the waves. @@ -48,7 +48,7 @@ Live Trading a Strategy ----------------------- This is the second part of the two-part series on using Catalyst for algorithmic -trading. Having backtested `our strategy `_ +trading. Having backtested `our strategy `_ in the previous video, we now take it to trade live against the Bittrex exchange. .. raw:: html From 7bbb6e0b42dd512a628f35de4ba57d83c87a22d1 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Thu, 30 Nov 2017 20:18:16 -0500 Subject: [PATCH 09/52] BLD: tested creating orders and viewing open orders with CCXT --- catalyst/examples/arbitrage_with_interface.py | 24 +-- catalyst/exchange/ccxt/ccxt_exchange.py | 167 +++++++++++++++++- catalyst/exchange/exchange.py | 98 +++------- catalyst/exchange/exchange_execution.py | 2 +- catalyst/utils/run_algo.py | 2 +- tests/exchange/test_ccxt.py | 8 +- 6 files changed, 198 insertions(+), 103 deletions(-) diff --git a/catalyst/examples/arbitrage_with_interface.py b/catalyst/examples/arbitrage_with_interface.py index f3625e7a..c0c0d343 100644 --- a/catalyst/examples/arbitrage_with_interface.py +++ b/catalyst/examples/arbitrage_with_interface.py @@ -83,15 +83,15 @@ def place_orders(context, amount, buying_price, selling_price, action): else: raise ValueError('invalid order action') - base_currency = enter_exchange.base_currency - base_currency_amount = enter_exchange.portfolio.cash + quote_currency = enter_exchange.quote_currency + quote_currency_amount = enter_exchange.portfolio.cash exit_balances = exit_exchange.get_balances() exit_currency = context.trading_pairs[ - context.selling_exchange].market_currency + context.selling_exchange].quote_currency if exit_currency in exit_balances: - market_currency_amount = exit_balances[exit_currency] + quote_currency_amount = exit_balances[exit_currency] else: log.warn( 'the selling exchange {exchange_name} does not hold ' @@ -102,25 +102,25 @@ def place_orders(context, amount, buying_price, selling_price, action): ) return - if base_currency_amount < (amount * entry_price): - adj_amount = base_currency_amount / entry_price + if quote_currency_amount < (amount * entry_price): + adj_amount = quote_currency_amount / entry_price log.warn( - 'not enough {base_currency} ({base_currency_amount}) to buy ' + 'not enough {quote_currency} ({quote_currency_amount}) to buy ' '{amount}, adjusting the amount to {adj_amount}'.format( - base_currency=base_currency, - base_currency_amount=base_currency_amount, + quote_currency=quote_currency, + quote_currency_amount=quote_currency_amount, amount=amount, adj_amount=adj_amount ) ) amount = adj_amount - elif market_currency_amount < amount: + elif quote_currency_amount < amount: log.warn( 'not enough {currency} ({currency_amount}) to sell ' '{amount}, aborting'.format( currency=exit_currency, - currency_amount=market_currency_amount, + currency_amount=quote_currency_amount, amount=amount ) ) @@ -270,6 +270,6 @@ run_algorithm( exchange_name='poloniex,bitfinex', live=True, algo_namespace=algo_namespace, - base_currency='btc', + quote_currency='btc', live_graph=False ) diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py index 58be745c..b0b9663b 100644 --- a/catalyst/exchange/ccxt/ccxt_exchange.py +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -3,19 +3,32 @@ from collections import defaultdict import ccxt import pandas as pd +from ccxt import ExchangeNotAvailable +from six import string_types + +from catalyst.finance.order import Order, ORDER_STATUS + +from catalyst.algorithm import MarketOrder from catalyst.assets._assets import TradingPair from logbook import Logger from catalyst.constants import LOG_LEVEL -from catalyst.exchange.exchange import Exchange +from catalyst.exchange.exchange import Exchange, ExchangeLimitOrder from catalyst.exchange.exchange_bundle import ExchangeBundle from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \ - ExchangeSymbolsNotFound + ExchangeSymbolsNotFound, ExchangeRequestError, InvalidOrderStyle from catalyst.exchange.exchange_utils import mixin_market_params, \ from_ms_timestamp log = Logger('CCXT', level=LOG_LEVEL) +SUPPORTED_EXCHANGES = dict( + binance=ccxt.binance, + bitfinex=ccxt.bitfinex, + bittrex=ccxt.bittrex, + poloniex=ccxt.poloniex, +) + class CCXT(Exchange): def __init__(self, exchange_name, key, secret, base_currency, @@ -26,7 +39,13 @@ class CCXT(Exchange): ) ) try: - exchange_attr = getattr(ccxt, exchange_name) + # Making instantiation as explicit as possible for code tracking. + if exchange_name in SUPPORTED_EXCHANGES: + exchange_attr = SUPPORTED_EXCHANGES[exchange_name] + + else: + exchange_attr = getattr(ccxt, exchange_name) + self.api = exchange_attr({ 'apiKey': key, 'secret': secret, @@ -62,8 +81,12 @@ class CCXT(Exchange): parts = asset.symbol.split('_') return '{}/{}'.format(parts[0].upper(), parts[1].upper()) - def get_catalyst_symbol(self, market): - parts = market['symbol'].split('/') + def get_catalyst_symbol(self, market_or_symbol): + symbol = market_or_symbol if isinstance( + market_or_symbol, string_types + ) else market_or_symbol['symbol'] + + parts = symbol.split('/') return '{}_{}'.format(parts[0].lower(), parts[1].lower()) def get_timeframe(self, freq): @@ -191,13 +214,141 @@ class CCXT(Exchange): self.assets[market['id']] = trading_pair def get_balances(self): - return None + try: + log.debug('retrieving wallets balances') + balances = self.api.fetch_balance() + + balances_lower = dict() + for key in balances: + balances_lower[key.lower()] = balances[key] + + except Exception as e: + log.debug('error retrieving balances: {}', e) + raise ExchangeRequestError(error=e) + + return balances_lower + + def _create_order(self, order_status): + """ + Create a Catalyst order object from a Bitfinex order dictionary + :param order_status: + :return: Order + """ + if order_status['status'] == 'canceled': + status = ORDER_STATUS.CANCELLED + + elif order_status['status'] == 'closed' and order_status['filled'] > 0: + log.info('found executed order {}'.format(order_status)) + status = ORDER_STATUS.FILLED + + elif order_status['status'] == 'open': + status = ORDER_STATUS.OPEN + + else: + raise ValueError('invalid state for order') + + amount = float(order_status['amount']) + filled = float(order_status['filled']) + + if order_status['side'] == 'sell': + amount = -amount + filled = -filled + + price = float(order_status['price']) + order_type = order_status['type'] + + stop_price = None + limit_price = None + + # TODO: is this comprehensive enough? + if order_type.endswith('limit'): + limit_price = price + elif order_type.endswith('stop'): + stop_price = price + + executed_price = order_status['cost'] / order_status['amount'] + commission = order_status['fee'] + date = from_ms_timestamp(order_status['timestamp']) + + symbol = order_status['info']['symbol'] + order = Order( + dt=date, + asset=self.assets[symbol], + amount=amount, + stop=stop_price, + limit=limit_price, + filled=filled, + id=str(order_status['id']), + commission=commission + ) + order.status = status + + return order, executed_price def create_order(self, asset, amount, is_buy, style): - return None + symbol = self.get_symbol(asset) + + if isinstance(style, ExchangeLimitOrder): + price = style.get_limit_price(is_buy) + order_type = 'limit' + + elif isinstance(style, MarketOrder): + price = None + order_type = 'market' + + else: + raise InvalidOrderStyle( + exchange=self.name, + style=style.__class__.__name__ + ) + + side = 'buy' if amount > 0 else 'sell' + + try: + result = self.api.create_order( + symbol=symbol, + type=order_type, + side=side, + amount=abs(amount), + price=price + ) + except ExchangeNotAvailable as e: + log.debug('unable to create order: {}'.format(e)) + raise ExchangeRequestError(error=e) + + if 'info' not in result: + raise ValueError('cannot use order without info attribute') + + order_id = str(result['info']['clientOrderId']) + order = Order( + dt=from_ms_timestamp(result['info']['transactTime']), + asset=asset, + amount=amount, + stop=style.get_stop_price(is_buy), + limit=style.get_limit_price(is_buy), + id=order_id + ) + return order def get_open_orders(self, asset): - return None + try: + symbol = self.get_symbol(asset) + result = self.api.fetch_open_orders( + symbol=symbol, + since=None, + limit=None, + params=dict() + ) + except Exception as e: + raise ExchangeRequestError(error=e) + + orders = [] + for order_status in result: + order, executed_price = self._create_order(order_status) + if asset is None or asset == order.sid: + orders.append(order) + + return orders def get_order(self, order_id): return None diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index 8a69d3fb..751955f0 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -8,15 +8,16 @@ import pandas as pd from catalyst.assets._assets import TradingPair from logbook import Logger +from catalyst.algorithm import MarketOrder from catalyst.constants import LOG_LEVEL from catalyst.data.data_portal import BASE_FIELDS from catalyst.exchange.bundle_utils import get_start_dt, \ get_delta, get_periods, get_periods_range from catalyst.exchange.exchange_bundle import ExchangeBundle from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \ - InvalidOrderStyle, BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \ + BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \ PricingDataNotLoadedError, \ - NoDataAvailableOnExchange, ExchangeSymbolsNotFound, NoValueForField + NoDataAvailableOnExchange, NoValueForField from catalyst.exchange.exchange_execution import ExchangeStopLimitOrder, \ ExchangeLimitOrder, ExchangeStopOrder from catalyst.exchange.exchange_portfolio import ExchangePortfolio @@ -34,7 +35,6 @@ class Exchange: def __init__(self): self.name = None self.assets = dict() - self.local_assets = dict() self._portfolio = None self.minute_writer = None self.minute_reader = None @@ -200,13 +200,13 @@ class Exchange: return assets def _find_asset(self, asset, symbol, data_frequency, is_local=False): - assets = self.assets if not is_local else self.local_assets - + assets = self.assets for key in assets: has_data = (data_frequency == 'minute' and assets[key].end_minute is not None) \ or (data_frequency == 'daily' and assets[key].end_daily is not None) + if not asset and assets[key].symbol.lower() == symbol.lower() \ and (not data_frequency or has_data): asset = assets[key] @@ -236,8 +236,7 @@ class Exchange: asset = self._find_asset(asset, symbol, data_frequency, True) if not asset: - all_values = list(self.assets.values()) + \ - list(self.local_assets.values()) + all_values = list(self.assets.values()) supported_symbols = sorted([ asset.symbol for asset in all_values ]) @@ -253,6 +252,7 @@ class Exchange: def fetch_symbol_map(self, is_local=False): return get_exchange_symbols(self.name, is_local) + @abstractmethod def load_assets(self, is_local=False): """ Populate the 'assets' attribute with a dictionary of Assets. @@ -270,66 +270,7 @@ class Exchange: via its api. """ - try: - symbol_map = self.fetch_symbol_map(is_local) - except ExchangeSymbolsNotFound: - return None - - for exchange_symbol in symbol_map: - asset = symbol_map[exchange_symbol] - - if 'start_date' in asset: - start_date = pd.to_datetime(asset['start_date'], utc=True) - else: - start_date = None - - if 'end_date' in asset: - end_date = pd.to_datetime(asset['end_date'], utc=True) - else: - end_date = None - - if 'leverage' in asset: - leverage = asset['leverage'] - else: - leverage = 1.0 - - if 'asset_name' in asset: - asset_name = asset['asset_name'] - else: - asset_name = None - - if 'min_trade_size' in asset: - min_trade_size = asset['min_trade_size'] - else: - min_trade_size = 0.0000001 - - if 'end_daily' in asset and asset['end_daily'] != 'N/A': - end_daily = pd.to_datetime(asset['end_daily'], utc=True) - else: - end_daily = None - - if 'end_minute' in asset and asset['end_minute'] != 'N/A': - end_minute = pd.to_datetime(asset['end_minute'], utc=True) - else: - end_minute = None - - trading_pair = TradingPair( - symbol=asset['symbol'], - exchange=self.name, - start_date=start_date, - end_date=end_date, - leverage=leverage, - asset_name=asset_name, - min_trade_size=min_trade_size, - end_daily=end_daily, - end_minute=end_minute, - exchange_symbol=exchange_symbol - ) - - if is_local: - self.local_assets[exchange_symbol] = trading_pair - else: - self.assets[exchange_symbol] = trading_pair + pass def check_open_orders(self): """ @@ -694,7 +635,7 @@ class Exchange: log.debug('synchronizing portfolio with exchange {}'.format(self.name)) balances = self.get_balances() - base_position_available = balances[self.base_currency] \ + base_position_available = balances[self.base_currency]['free'] \ if self.base_currency in balances else None if base_position_available is None: @@ -777,28 +718,30 @@ class Exchange: log.warn('skipping order amount of 0') return None - if asset.base_currency != self.base_currency.lower(): + if self.base_currency is None: + raise ValueError('no base_currency defined for this exchange') + + if asset.quote_currency != self.base_currency.lower(): raise MismatchingBaseCurrencies( - base_currency=asset.base_currency, + base_currency=asset.quote_currency, algo_currency=self.base_currency ) is_buy = (amount > 0) if limit_price is not None and stop_price is not None: - style = ExchangeStopLimitOrder(limit_price, stop_price, - exchange=self.name) + style = ExchangeStopLimitOrder( + limit_price, stop_price, exchange=self.name + ) + elif limit_price is not None: style = ExchangeLimitOrder(limit_price, exchange=self.name) elif stop_price is not None: style = ExchangeStopOrder(stop_price, exchange=self.name) - elif style is not None: - raise InvalidOrderStyle(exchange=self.name.title(), - style=style.__class__.__name__) else: - raise ValueError('Incomplete order data.') + style = MarketOrder(exchange=self.name) display_price = limit_price if limit_price is not None else stop_price log.debug( @@ -807,9 +750,10 @@ class Exchange: amount=amount, symbol=asset.symbol, type=style.__class__.__name__, - price='{}{}'.format(display_price, asset.base_currency) + price='{}{}'.format(display_price, asset.quote_currency) ) ) + order = self.create_order(asset, amount, is_buy, style) if order: self._portfolio.create_order(order) diff --git a/catalyst/exchange/exchange_execution.py b/catalyst/exchange/exchange_execution.py index 536b526a..fe029e3c 100644 --- a/catalyst/exchange/exchange_execution.py +++ b/catalyst/exchange/exchange_execution.py @@ -1,4 +1,4 @@ -from catalyst.finance.execution import LimitOrder, StopOrder, StopLimitOrder +from catalyst.finance.execution import LimitOrder, StopOrder, StopLimitOrder, MarketOrder class ExchangeLimitOrder(LimitOrder): diff --git a/catalyst/utils/run_algo.py b/catalyst/utils/run_algo.py index af62f97d..288c09bc 100644 --- a/catalyst/utils/run_algo.py +++ b/catalyst/utils/run_algo.py @@ -263,7 +263,7 @@ def _run(handle_data, ) if base_currency in balances: - base_currency_available = balances[base_currency] + base_currency_available = balances[base_currency]['free'] log.info( 'base currency available in the account: {} {}'.format( base_currency_available, base_currency diff --git a/tests/exchange/test_ccxt.py b/tests/exchange/test_ccxt.py index f00ea049..a0a027d6 100644 --- a/tests/exchange/test_ccxt.py +++ b/tests/exchange/test_ccxt.py @@ -21,16 +21,16 @@ class TestCCXT(BaseExchangeTestCase): exchange_name=exchange_name, key=auth['key'], secret=auth['secret'], - base_currency=None, + base_currency='eth', portfolio=None ) def test_order(self): log.info('creating order') - asset = self.exchange.get_asset('neo_btc') + asset = self.exchange.get_asset('neo_eth') order_id = self.exchange.order( asset=asset, - limit_price=0.0005, + limit_price=0.07, amount=1, ) log.info('order created {}'.format(order_id)) @@ -39,7 +39,7 @@ class TestCCXT(BaseExchangeTestCase): def test_open_orders(self): log.info('retrieving open orders') - asset = self.exchange.get_asset('neo_btc') + asset = self.exchange.get_asset('neo_eth') orders = self.exchange.get_open_orders(asset) pass From b762689225b6dffa506c10288bb2ca47fa0a6163 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Thu, 30 Nov 2017 22:45:52 -0500 Subject: [PATCH 10/52] BLD: all exchange operations now implemented an unit tested with CCXT --- catalyst/exchange/ccxt/ccxt_exchange.py | 99 +++++++++++++++++++------ catalyst/exchange/exchange.py | 16 ++-- catalyst/exchange/exchange_portfolio.py | 30 +------- tests/exchange/test_ccxt.py | 6 +- 4 files changed, 92 insertions(+), 59 deletions(-) diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py index b0b9663b..6db774c1 100644 --- a/catalyst/exchange/ccxt/ccxt_exchange.py +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -77,8 +77,12 @@ class CCXT(Exchange): def time_skew(self): return None - def get_symbol(self, asset): - parts = asset.symbol.split('_') + def get_symbol(self, asset_or_symbol): + symbol = asset_or_symbol if isinstance( + asset_or_symbol, string_types + ) else asset_or_symbol.symbol + + parts = symbol.split('_') return '{}/{}'.format(parts[0].upper(), parts[1].upper()) def get_catalyst_symbol(self, market_or_symbol): @@ -230,15 +234,24 @@ class CCXT(Exchange): def _create_order(self, order_status): """ - Create a Catalyst order object from a Bitfinex order dictionary - :param order_status: - :return: Order + Create a Catalyst order object from a CCXT order dictionary + + Parameters + ---------- + order_status: dict[str, Object] + The order dict from the CCXT api. + + Returns + ------- + Order + The Catalyst order object + """ if order_status['status'] == 'canceled': status = ORDER_STATUS.CANCELLED elif order_status['status'] == 'closed' and order_status['filled'] > 0: - log.info('found executed order {}'.format(order_status)) + log.debug('found executed order {}'.format(order_status)) status = ORDER_STATUS.FILLED elif order_status['status'] == 'open': @@ -247,30 +260,27 @@ class CCXT(Exchange): else: raise ValueError('invalid state for order') - amount = float(order_status['amount']) - filled = float(order_status['filled']) + amount = order_status['amount'] + filled = order_status['filled'] if order_status['side'] == 'sell': amount = -amount filled = -filled - price = float(order_status['price']) + price = order_status['price'] order_type = order_status['type'] - stop_price = None - limit_price = None - - # TODO: is this comprehensive enough? - if order_type.endswith('limit'): - limit_price = price - elif order_type.endswith('stop'): - stop_price = price + limit_price = price if order_type == 'limit' else None + stop_price = None # TODO: add support executed_price = order_status['cost'] / order_status['amount'] commission = order_status['fee'] date = from_ms_timestamp(order_status['timestamp']) + # order_id = str(order_status['info']['clientOrderId']) + order_id = order_status['id'] symbol = order_status['info']['symbol'] + order = Order( dt=date, asset=self.assets[symbol], @@ -278,7 +288,7 @@ class CCXT(Exchange): stop=stop_price, limit=limit_price, filled=filled, - id=str(order_status['id']), + id=order_id, commission=commission ) order.status = status @@ -319,7 +329,8 @@ class CCXT(Exchange): if 'info' not in result: raise ValueError('cannot use order without info attribute') - order_id = str(result['info']['clientOrderId']) + # order_id = str(result['info']['clientOrderId']) + order_id = result['id'] order = Order( dt=from_ms_timestamp(result['info']['transactTime']), asset=asset, @@ -350,11 +361,53 @@ class CCXT(Exchange): return orders - def get_order(self, order_id): - return None + def _get_asset_from_order(self, order_id): + open_orders = self.portfolio.open_orders + order = next( + (order for order in open_orders if order.id == order_id), + None + ) # type: Order + return order.asset if order is not None else None - def cancel_order(self, order_param): - return None + def get_order(self, order_id, asset_or_symbol=None): + if asset_or_symbol is None and self.portfolio is not None: + asset_or_symbol = self._get_asset_from_order(order_id) + + if asset_or_symbol is None: + log.debug( + 'order not found in memory, the request might fail ' + 'on some exchanges.' + ) + try: + symbol = self.get_symbol(asset_or_symbol) \ + if asset_or_symbol is not None else None + order_status = self.api.fetch_order(id=order_id, symbol=symbol) + order, _ = self._create_order(order_status) + + except Exception as e: + raise ExchangeRequestError(error=e) + + return order + + def cancel_order(self, order_param, asset_or_symbol=None): + order_id = order_param.id \ + if isinstance(order_param, Order) else order_param + + if asset_or_symbol is None and self.portfolio is not None: + asset_or_symbol = self._get_asset_from_order(order_id) + + if asset_or_symbol is None: + log.debug( + 'order not found in memory, cancelling order might fail ' + 'on some exchanges.' + ) + try: + symbol = self.get_symbol(asset_or_symbol) \ + if asset_or_symbol is not None else None + self.api.cancel_order(id=order_id, symbol=symbol) + + except Exception as e: + raise ExchangeRequestError(error=e) def tickers(self, assets): """ diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index 751955f0..fd2be234 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -289,9 +289,11 @@ class Exchange: log.debug('found open order: {}'.format(order_id)) order, executed_price = self.get_order(order_id) - log.debug('got updated order {} {}'.format( - order, executed_price)) - + log.debug( + 'got updated order {} {}'.format( + order, executed_price + ) + ) if order.status == ORDER_STATUS.FILLED: transaction = Transaction( asset=order.asset, @@ -822,7 +824,7 @@ class Exchange: pass @abstractmethod - def get_order(self, order_id): + def get_order(self, order_id, symbol_or_asset=None): """Lookup an order based on the order id returned from one of the order functions. @@ -830,6 +832,8 @@ class Exchange: ---------- order_id : str The unique identifier for the order. + symbol_or_asset: str|TradingPair + The catalyst symbol, some exchanges need this Returns ------- @@ -841,13 +845,15 @@ class Exchange: pass @abstractmethod - def cancel_order(self, order_param): + def cancel_order(self, order_param, symbol_or_asset=None): """Cancel an open order. Parameters ---------- order_param : str or Order The order_id or order object to cancel. + symbol_or_asset: str|TradingPair + The catalyst symbol, some exchanges need this """ pass diff --git a/catalyst/exchange/exchange_portfolio.py b/catalyst/exchange/exchange_portfolio.py index 2003654b..b9f45fbb 100644 --- a/catalyst/exchange/exchange_portfolio.py +++ b/catalyst/exchange/exchange_portfolio.py @@ -3,7 +3,6 @@ from logbook import Logger from catalyst.constants import LOG_LEVEL from catalyst.protocol import Portfolio, Positions, Position -from catalyst.utils.deprecate import deprecated log = Logger('ExchangePortfolio', level=LOG_LEVEL) @@ -11,7 +10,8 @@ log = Logger('ExchangePortfolio', level=LOG_LEVEL) class ExchangePortfolio(Portfolio): """ Since the goal is to support multiple exchanges, it makes sense to - include additional stats in the portfolio object. + include additional stats in the portfolio object. This fills the role + of Blotter and Portfolio in live mode. Instead of relying on the performance tracker, each exchange portfolio tracks its own holding. This offers a separation between tracking an @@ -89,32 +89,6 @@ class ExchangePortfolio(Portfolio): log.debug('updated portfolio with executed order') - @deprecated - def execute_transaction(self, transaction): - # TODO: almost duplicate of execute_order. Not sure why Poloniex needs this. - log.debug('executing transaction {}'.format(transaction.order_id)) - - order_position = self.positions[transaction.asset] \ - if transaction.asset in self.positions else None - - if order_position is None: - raise ValueError( - 'Trying to execute transaction for a position not held: %s' % transaction.order_id - ) - - self.capital_used += transaction.amount * transaction.price - - if transaction.amount > 0: - if order_position.cost_basis > 0: - order_position.cost_basis = np.average( - [order_position.cost_basis, transaction.price], - weights=[order_position.amount, transaction.amount] - ) - else: - order_position.cost_basis = transaction.price - - log.debug('updated portfolio with executed order') - def remove_order(self, order): """ Removing an open order. diff --git a/tests/exchange/test_ccxt.py b/tests/exchange/test_ccxt.py index a0a027d6..eccdd18f 100644 --- a/tests/exchange/test_ccxt.py +++ b/tests/exchange/test_ccxt.py @@ -45,14 +45,14 @@ class TestCCXT(BaseExchangeTestCase): def test_get_order(self): log.info('retrieving order') - order = self.exchange.get_order( - u'2c584020-9caf-4af5-bde0-332c0bba17e2') + order = self.exchange.get_order('2631386', 'neo_eth') + # order = self.exchange.get_order('2631386') assert isinstance(order, Order) pass def test_cancel_order(self, ): log.info('cancel order') - self.exchange.cancel_order(u'dc7bcca2-5219-4145-8848-8a593d2a72f9') + self.exchange.cancel_order('2631386', 'neo_eth') pass def test_get_candles(self): From 8fb8b80a12ffd9887a760b93704b8f9947c8a44a Mon Sep 17 00:00:00 2001 From: fredfortier Date: Thu, 30 Nov 2017 23:15:10 -0500 Subject: [PATCH 11/52] BLD: first rough test of CCXT in live trading --- catalyst/data/loader.py | 6 ++- catalyst/examples/simple_loop.py | 56 ++++++++++++------------- catalyst/exchange/ccxt/ccxt_exchange.py | 43 ++++++++++--------- catalyst/exchange/exchange.py | 4 +- catalyst/exchange/factory.py | 53 ++++++++++------------- catalyst/utils/run_algo.py | 42 ++++--------------- 6 files changed, 88 insertions(+), 116 deletions(-) diff --git a/catalyst/data/loader.py b/catalyst/data/loader.py index ff3f34a8..e58127f7 100644 --- a/catalyst/data/loader.py +++ b/catalyst/data/loader.py @@ -142,8 +142,10 @@ def load_crypto_market_data(trading_day=None, trading_days=None, if exchange is None: # This is exceptional, since placing the import at the module scope # breaks things and it's only needed here - from catalyst.exchange.poloniex.poloniex import Poloniex - exchange = Poloniex('', '', '') + from catalyst.exchange.factory import get_exchange + exchange = get_exchange( + exchange_name='poloniex', base_currency='usdt' + ) benchmark_asset = exchange.get_asset(bm_symbol) diff --git a/catalyst/examples/simple_loop.py b/catalyst/examples/simple_loop.py index bfd2d4f0..75f55092 100644 --- a/catalyst/examples/simple_loop.py +++ b/catalyst/examples/simple_loop.py @@ -9,7 +9,7 @@ from catalyst.exchange.stats_utils import get_pretty_stats, \ def initialize(context): print('initializing') - context.asset = symbol('neo_usd') + context.asset = symbol('neo_eth') context.base_price = None @@ -19,17 +19,14 @@ def handle_data(context, data): price = data.current(context.asset, 'close') print('got price {price}'.format(price=price)) - try: - prices = data.history( - context.asset, - fields='price', - bar_count=14, - frequency='15T' - ) - rsi = talib.RSI(prices.values, timeperiod=14)[-1] - print('got rsi: {}'.format(rsi)) - except Exception as e: - print(e) + prices = data.history( + context.asset, + fields='price', + bar_count=20, + frequency='15T' + ) + rsi = talib.RSI(prices.values, timeperiod=14)[-1] + print('got rsi: {}'.format(rsi)) # If base_price is not set, we use the current value. This is the # price at the first bar which we reference to calculate price_change. @@ -110,24 +107,25 @@ def analyze(context, perf): pass -run_algorithm( - capital_base=250, - start=pd.to_datetime('2017-11-1 0:00', utc=True), - end=pd.to_datetime('2017-11-10 23:59', utc=True), - data_frequency='daily', - initialize=initialize, - handle_data=handle_data, - analyze=analyze, - exchange_name='bitfinex', - algo_namespace='simple_loop', - base_currency='usd' -) # run_algorithm( +# capital_base=250, +# start=pd.to_datetime('2017-11-1 0:00', utc=True), +# end=pd.to_datetime('2017-11-10 23:59', utc=True), +# data_frequency='daily', # initialize=initialize, # handle_data=handle_data, -# analyze=None, -# exchange_name='poloniex', -# live=True, +# analyze=analyze, +# exchange_name='bitfinex', # algo_namespace='simple_loop', -# base_currency='eth', -# live_graph=False +# base_currency='usd' +# ) +run_algorithm( + initialize=initialize, + handle_data=handle_data, + analyze=None, + exchange_name='binance', + live=True, + algo_namespace='simple_loop', + base_currency='eth', + live_graph=False, +) diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py index 6db774c1..d04c346f 100644 --- a/catalyst/exchange/ccxt/ccxt_exchange.py +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -16,7 +16,8 @@ from catalyst.constants import LOG_LEVEL from catalyst.exchange.exchange import Exchange, ExchangeLimitOrder from catalyst.exchange.exchange_bundle import ExchangeBundle from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \ - ExchangeSymbolsNotFound, ExchangeRequestError, InvalidOrderStyle + ExchangeSymbolsNotFound, ExchangeRequestError, InvalidOrderStyle, \ + ExchangeNotFoundError from catalyst.exchange.exchange_utils import mixin_market_params, \ from_ms_timestamp @@ -50,8 +51,9 @@ class CCXT(Exchange): 'apiKey': key, 'secret': secret, }) + except Exception: - raise ValueError('exchange not in CCXT') + raise ExchangeNotFoundError(exchange_name=exchange_name) markets = self.api.load_markets() log.debug('the markets:\n{}'.format(markets)) @@ -122,24 +124,27 @@ class CCXT(Exchange): delta = start_dt - pd.to_datetime('1970-1-1', utc=True) ms = int(delta.total_seconds()) * 1000 - ohlcvs = self.api.fetch_ohlcv( - symbol=symbols[0], - timeframe=timeframe, - since=ms, - limit=bar_count, - params={} - ) + candles = dict() + for asset in assets: + ohlcvs = self.api.fetch_ohlcv( + symbol=symbols[0], + timeframe=timeframe, + since=ms, + limit=bar_count, + params={} + ) + + candles[asset] = [] + for ohlcv in ohlcvs: + candles[asset].append(dict( + last_traded=pd.to_datetime(ohlcv[0], unit='ms', utc=True), + open=ohlcv[1], + high=ohlcv[2], + low=ohlcv[3], + close=ohlcv[4], + volume=ohlcv[5] + )) - candles = [] - for ohlcv in ohlcvs: - candles.append(dict( - last_traded=pd.to_datetime(ohlcv[0], unit='ms', utc=True), - open=ohlcv[1], - high=ohlcv[2], - low=ohlcv[3], - close=ohlcv[4], - volume=ohlcv[5] - )) return candles def _fetch_symbol_map(self, is_local): diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index fd2be234..317175d0 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -357,10 +357,10 @@ class Exchange: tickers = self.tickers(assets) if field == 'close' or field == 'price': - return [t['last'] for t in tickers] + return [tickers[asset]['last'] for asset in tickers] elif field == 'volume': - return [t['volume'] for t in tickers] + return [tickers[asset]['volume'] for asset in tickers] else: raise NoValueForField(field=field) diff --git a/catalyst/exchange/factory.py b/catalyst/exchange/factory.py index 72c66bd8..8099b208 100644 --- a/catalyst/exchange/factory.py +++ b/catalyst/exchange/factory.py @@ -1,38 +1,31 @@ -from catalyst.exchange.bitfinex.bitfinex import Bitfinex -from catalyst.exchange.bittrex.bittrex import Bittrex -from catalyst.exchange.exchange_errors import ExchangeNotFoundError -from catalyst.exchange.exchange_utils import get_exchange_auth -from catalyst.exchange.poloniex.poloniex import Poloniex +import os + +from catalyst.exchange.ccxt.ccxt_exchange import CCXT +from catalyst.exchange.exchange_errors import ExchangeAuthEmpty +from catalyst.exchange.exchange_utils import get_exchange_auth, \ + get_exchange_folder -def get_exchange(exchange_name, base_currency=None): +def get_exchange(exchange_name, base_currency=None, portfolio=None, + must_authenticate=False): exchange_auth = get_exchange_auth(exchange_name) - if exchange_name == 'bitfinex': - return Bitfinex( - key=exchange_auth['key'], - secret=exchange_auth['secret'], - base_currency=base_currency, - portfolio=None + + has_auth = (exchange_auth['key'] != '' and exchange_auth['secret'] != '') + if must_authenticate and not has_auth: + raise ExchangeAuthEmpty( + exchange=exchange_name.title(), + filename=os.path.join( + get_exchange_folder(exchange_name), 'auth.json' + ) ) - elif exchange_name == 'bittrex': - return Bittrex( - key=exchange_auth['key'], - secret=exchange_auth['secret'], - base_currency=base_currency, - portfolio=None - ) - - elif exchange_name == 'poloniex': - return Poloniex( - key=exchange_auth['key'], - secret=exchange_auth['secret'], - base_currency=base_currency, - portfolio=None - ) - - else: - raise ExchangeNotFoundError(exchange_name=exchange_name) + return CCXT( + exchange_name=exchange_name, + key=exchange_auth['key'], + secret=exchange_auth['secret'], + base_currency=base_currency, + portfolio=portfolio + ) def get_exchanges(exchange_names): diff --git a/catalyst/utils/run_algo.py b/catalyst/utils/run_algo.py index 288c09bc..404b21c0 100644 --- a/catalyst/utils/run_algo.py +++ b/catalyst/utils/run_algo.py @@ -13,6 +13,7 @@ from catalyst.data.bundles import load from catalyst.data.data_portal import DataPortal from catalyst.exchange.bittrex.bittrex import Bittrex from catalyst.exchange.bitfinex.bitfinex import Bitfinex +from catalyst.exchange.factory import get_exchange from catalyst.exchange.poloniex.poloniex import Poloniex try: @@ -164,42 +165,15 @@ def _run(handle_data, if portfolio is None: portfolio = ExchangePortfolio( - start_date=pd.Timestamp.utcnow() + start if start is not None else pd.Timestamp.utcnow() ) - # This corresponds to the json file containing api token info - exchange_auth = get_exchange_auth(exchange_name) - - if live and (exchange_auth['key'] == '' \ - or exchange_auth['secret'] == ''): - raise ExchangeAuthEmpty( - exchange=exchange_name.title(), - filename=os.path.join( - get_exchange_folder(exchange_name, environ), 'auth.json')) - - if exchange_name == 'bitfinex': - exchanges[exchange_name] = Bitfinex( - key=exchange_auth['key'], - secret=exchange_auth['secret'], - base_currency=base_currency, - portfolio=portfolio - ) - elif exchange_name == 'bittrex': - exchanges[exchange_name] = Bittrex( - key=exchange_auth['key'], - secret=exchange_auth['secret'], - base_currency=base_currency, - portfolio=portfolio - ) - elif exchange_name == 'poloniex': - exchanges[exchange_name] = Poloniex( - key=exchange_auth['key'], - secret=exchange_auth['secret'], - base_currency=base_currency, - portfolio=portfolio - ) - else: - raise ExchangeNotFoundError(exchange_name=exchange_name) + exchanges[exchange_name] = get_exchange( + exchange_name=exchange_name, + base_currency=base_currency, + portfolio=portfolio, + must_authenticate=live, + ) open_calendar = get_calendar('OPEN') From 4db8131397e223be84b5dd3c05423f0b64d80f5a Mon Sep 17 00:00:00 2001 From: fredfortier Date: Fri, 1 Dec 2017 00:06:11 -0500 Subject: [PATCH 12/52] BLD: paper trading adjustments --- catalyst/examples/simple_loop.py | 34 ++++++++++++------------- catalyst/exchange/ccxt/ccxt_exchange.py | 15 +++++++---- catalyst/exchange/exchange_algorithm.py | 1 + catalyst/exchange/exchange_utils.py | 13 ++++++---- catalyst/utils/run_algo.py | 21 ++++++++------- tests/exchange/test_ccxt.py | 18 ++++--------- 6 files changed, 51 insertions(+), 51 deletions(-) diff --git a/catalyst/examples/simple_loop.py b/catalyst/examples/simple_loop.py index 75f55092..b0dbd929 100644 --- a/catalyst/examples/simple_loop.py +++ b/catalyst/examples/simple_loop.py @@ -107,25 +107,25 @@ def analyze(context, perf): pass -# run_algorithm( -# capital_base=250, -# start=pd.to_datetime('2017-11-1 0:00', utc=True), -# end=pd.to_datetime('2017-11-10 23:59', utc=True), -# data_frequency='daily', -# initialize=initialize, -# handle_data=handle_data, -# analyze=analyze, -# exchange_name='bitfinex', -# algo_namespace='simple_loop', -# base_currency='usd' -# ) run_algorithm( + capital_base=250, + start=pd.to_datetime('2017-11-1 0:00', utc=True), + end=pd.to_datetime('2017-11-10 23:59', utc=True), + data_frequency='daily', initialize=initialize, handle_data=handle_data, - analyze=None, - exchange_name='binance', - live=True, + analyze=analyze, + exchange_name='bitfinex', algo_namespace='simple_loop', - base_currency='eth', - live_graph=False, + base_currency='usd' ) +# run_algorithm( +# initialize=initialize, +# handle_data=handle_data, +# analyze=None, +# exchange_name='binance', +# live=True, +# algo_namespace='simple_loop', +# base_currency='eth', +# live_graph=False, +# ) diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py index d04c346f..d2c735cc 100644 --- a/catalyst/exchange/ccxt/ccxt_exchange.py +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -28,6 +28,8 @@ SUPPORTED_EXCHANGES = dict( bitfinex=ccxt.bitfinex, bittrex=ccxt.bittrex, poloniex=ccxt.poloniex, + bitmex=ccxt.bitmex, + gdax=ccxt.gdax, ) @@ -88,12 +90,15 @@ class CCXT(Exchange): return '{}/{}'.format(parts[0].upper(), parts[1].upper()) def get_catalyst_symbol(self, market_or_symbol): - symbol = market_or_symbol if isinstance( - market_or_symbol, string_types - ) else market_or_symbol['symbol'] + if isinstance(market_or_symbol, string_types): + parts = market_or_symbol.split('/') + return '{}_{}'.format(parts[0].lower(), parts[1].lower()) - parts = symbol.split('/') - return '{}_{}'.format(parts[0].lower(), parts[1].lower()) + else: + return '{}_{}'.format( + market_or_symbol['base'].lower(), + market_or_symbol['quote'].lower(), + ) def get_timeframe(self, freq): freq_match = re.match(r'([0-9].*)?(m|M|d|D|h|H|T)', freq, re.M | re.I) diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index bd7c84a1..c2102015 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -289,6 +289,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): def __init__(self, *args, **kwargs): self.algo_namespace = kwargs.pop('algo_namespace', None) self.live_graph = kwargs.pop('live_graph', None) + self.simulate_orders = kwargs.pop('simulate_orders', None) self._clock = None self.frame_stats = deque(maxlen=60) diff --git a/catalyst/exchange/exchange_utils.py b/catalyst/exchange/exchange_utils.py index ee0ce44e..0ca18c95 100644 --- a/catalyst/exchange/exchange_utils.py +++ b/catalyst/exchange/exchange_utils.py @@ -594,12 +594,15 @@ def mixin_market_params(exchange_name, params, market): params['maker'] = 0.001 params['taker'] = 0.002 - else: - if 'maker' in market: - params['maker'] = market['maker'] + elif 'maker' in market and 'taker' in market \ + and market['maker'] is not None and market['taker'] is not None: + params['maker'] = market['maker'] + params['taker'] = market['taker'] - if 'taker' in market: - params['taker'] = market['taker'] + else: + # TODO: default commission, make configurable + params['maker'] = 0.0015 + params['taker'] = 0.0025 info = market['info'] if 'info' in market else None if info: diff --git a/catalyst/utils/run_algo.py b/catalyst/utils/run_algo.py index 404b21c0..a20490bc 100644 --- a/catalyst/utils/run_algo.py +++ b/catalyst/utils/run_algo.py @@ -11,10 +11,7 @@ import pandas as pd from catalyst.data.bundles import load from catalyst.data.data_portal import DataPortal -from catalyst.exchange.bittrex.bittrex import Bittrex -from catalyst.exchange.bitfinex.bitfinex import Bitfinex from catalyst.exchange.factory import get_exchange -from catalyst.exchange.poloniex.poloniex import Poloniex try: from pygments import highlight @@ -40,11 +37,9 @@ from catalyst.exchange.exchange_data_portal import DataPortalExchangeLive, \ from catalyst.exchange.asset_finder_exchange import AssetFinderExchange from catalyst.exchange.exchange_portfolio import ExchangePortfolio from catalyst.exchange.exchange_errors import ( - ExchangeRequestError, ExchangeAuthEmpty, - ExchangeRequestErrorTooManyAttempts, - BaseCurrencyNotFoundError, ExchangeNotFoundError) -from catalyst.exchange.exchange_utils import get_exchange_auth, \ - get_algo_object, get_exchange_folder + ExchangeRequestError, ExchangeRequestErrorTooManyAttempts, + BaseCurrencyNotFoundError) +from catalyst.exchange.exchange_utils import get_algo_object from logbook import Logger from catalyst.constants import LOG_LEVEL @@ -95,7 +90,8 @@ def _run(handle_data, exchange, algo_namespace, base_currency, - live_graph): + live_graph, + simulate_orders): """Run a backtest for the given algorithm. This is shared between the cli and :func:`catalyst.run_algo`. @@ -282,7 +278,8 @@ def _run(handle_data, ExchangeTradingAlgorithmLive, exchanges=exchanges, algo_namespace=algo_namespace, - live_graph=live_graph + live_graph=live_graph, + simulate_orders=simulate_orders ) elif exchanges: # Removed the existing Poloniex fork to keep things simple @@ -444,6 +441,7 @@ def run_algorithm(initialize, base_currency=None, algo_namespace=None, live_graph=False, + simulate_orders=True, output=os.devnull): """Run a trading algorithm. @@ -565,5 +563,6 @@ def run_algorithm(initialize, exchange=exchange_name, algo_namespace=algo_namespace, base_currency=base_currency, - live_graph=live_graph + live_graph=live_graph, + simulate_orders=simulate_orders ) diff --git a/tests/exchange/test_ccxt.py b/tests/exchange/test_ccxt.py index eccdd18f..b1df6a19 100644 --- a/tests/exchange/test_ccxt.py +++ b/tests/exchange/test_ccxt.py @@ -15,7 +15,7 @@ log = Logger('test_ccxt') class TestCCXT(BaseExchangeTestCase): @classmethod def setup(self): - exchange_name = 'binance' + exchange_name = 'gdax' auth = get_exchange_auth(exchange_name) self.exchange = CCXT( exchange_name=exchange_name, @@ -64,25 +64,17 @@ class TestCCXT(BaseExchangeTestCase): start_dt=pd.to_datetime('2017-01-01', utc=True) ) - df = pd.DataFrame(candles) - df.set_index('last_traded', drop=True, inplace=True) - - folder = os.path.join( - tempfile.gettempdir(), 'catalyst', self.exchange.name, 'eth_btc' - ) - ensure_directory(folder) - - path = os.path.join(folder, 'output.csv') - df.to_csv(path) + for asset in candles: + df = pd.DataFrame(candles[asset]) + df.set_index('last_traded', drop=True, inplace=True) pass def test_tickers(self): log.info('retrieving tickers') tickers = self.exchange.tickers([ self.exchange.get_asset('eth_btc'), - self.exchange.get_asset('etc_btc') ]) - assert len(tickers) == 2 + assert len(tickers) == 1 pass def test_get_balances(self): From a3838fc00f89597f4ee8d65df733aa0ba3ce62ea Mon Sep 17 00:00:00 2001 From: fredfortier Date: Sat, 2 Dec 2017 20:20:57 -0500 Subject: [PATCH 13/52] BLD: tested ccxt with manual data ingestion --- catalyst/constants.py | 3 +- catalyst/exchange/ccxt/ccxt_exchange.py | 154 +++++++++++++++--------- catalyst/exchange/exchange.py | 54 +++++---- catalyst/exchange/exchange_bundle.py | 68 ++++++----- catalyst/exchange/exchange_utils.py | 19 ++- 5 files changed, 183 insertions(+), 115 deletions(-) diff --git a/catalyst/constants.py b/catalyst/constants.py index 35e1f727..6372b11f 100644 --- a/catalyst/constants.py +++ b/catalyst/constants.py @@ -7,7 +7,8 @@ import logbook For example, if you want to see the DEBUG messages, run: $ export CATALYST_LOG_LEVEL=10 ''' -LOG_LEVEL = int(os.environ.get('CATALYST_LOG_LEVEL', logbook.INFO)) +# LOG_LEVEL = int(os.environ.get('CATALYST_LOG_LEVEL', logbook.INFO)) +LOG_LEVEL = logbook.DEBUG SYMBOLS_URL = 'https://s3.amazonaws.com/enigmaco/catalyst-exchanges/' \ '{exchange}/symbols.json' diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py index d2c735cc..4d5b3939 100644 --- a/catalyst/exchange/ccxt/ccxt_exchange.py +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -57,12 +57,14 @@ class CCXT(Exchange): except Exception: raise ExchangeNotFoundError(exchange_name=exchange_name) - markets = self.api.load_markets() - log.debug('the markets:\n{}'.format(markets)) + self._symbol_maps = [None, None] + + markets_symbols = self.api.load_markets() + log.debug('the markets:\n{}'.format(markets_symbols)) self.name = exchange_name - self.assets = dict() + self.markets = self.api.fetch_markets() self.load_assets() self.base_currency = base_currency @@ -81,6 +83,14 @@ class CCXT(Exchange): def time_skew(self): return None + def get_market(self, symbol): + s = self.get_symbol(symbol) + market = next( + (market for market in self.markets if market['symbol'] == s), + None, + ) + return market + def get_symbol(self, asset_or_symbol): symbol = asset_or_symbol if isinstance( asset_or_symbol, string_types @@ -158,74 +168,102 @@ class CCXT(Exchange): except ExchangeSymbolsNotFound: return None - def _fetch_asset(self, market_id, is_local=False): + def fetch_asset_defs(self, market): + asset_defs = [] + + for is_local in (False, True): + asset_def = self.fetch_asset_def(market, is_local) + asset_defs.append((asset_def, is_local)) + + return asset_defs + + def fetch_asset_def(self, market, is_local=False): + exchange_symbol = market['id'] + symbol_map = self._fetch_symbol_map(is_local) if symbol_map is not None: assets_lower = {k.lower(): v for k, v in symbol_map.items()} - key = market_id.lower() + key = exchange_symbol.lower() asset = assets_lower[key] if key in assets_lower else None if asset is not None: - return asset, is_local - - elif not is_local: - return self._fetch_asset(market_id, True) + return asset else: - return None, is_local - - elif not is_local: - return self._fetch_asset(market_id, True) + return None else: - return None, is_local + return None + + def create_trading_pair(self, market, asset_def, is_local): + """ + Creating a TradingPair from market and asset data. + + Parameters + ---------- + market: dict[str, Object] + asset_def: dict[str, Object] + is_local: bool + + Returns + ------- + + """ + data_source = 'local' if is_local else 'catalyst' + params = dict( + exchange=self.name, + data_source=data_source, + exchange_symbol=market['id'], + ) + mixin_market_params(self.name, params, market) + + if asset_def is not None: + params['symbol'] = asset_def['symbol'] + + params['start_date'] = asset_def['start_date'] \ + if 'start_date' in asset_def else None + + params['end_date'] = asset_def['end_date'] \ + if 'end_date' in asset_def else None + + params['leverage'] = asset_def['leverage'] \ + if 'leverage' in asset_def else 1.0 + + params['asset_name'] = asset_def['asset_name'] \ + if 'asset_name' in asset_def else None + + params['end_daily'] = asset_def['end_daily'] \ + if 'end_daily' in asset_def \ + and asset_def['end_daily'] != 'N/A' else None + + params['end_minute'] = asset_def['end_minute'] \ + if 'end_minute' in asset_def \ + and asset_def['end_minute'] != 'N/A' else None + + else: + params['symbol'] = self.get_catalyst_symbol(market) + params['leverage'] = 1.0 + + return TradingPair(**params) def load_assets(self): - markets = self.api.fetch_markets() + self.assets = [] - for market in markets: - asset, is_local = self._fetch_asset(market['id']) - data_source = 'local' if is_local else 'catalyst' + for market in self.markets: + log.debug('fetching asset for market: {}'.format(market['id'])) + asset_defs = self.fetch_asset_defs(market) - params = dict( - exchange=self.name, - data_source=data_source, - exchange_symbol=market['id'], - ) - mixin_market_params(self.name, params, market) - - if asset is not None: - params['symbol'] = asset['symbol'] - - params['start_date'] = pd.to_datetime( - asset['start_date'], utc=True - ) if 'start_date' in asset else None - - params['end_date'] = pd.to_datetime( - asset['end_date'], utc=True - ) if 'end_date' in asset else None - - params['leverage'] = asset['leverage'] \ - if 'leverage' in asset else 1.0 - - params['asset_name'] = asset['asset_name'] \ - if 'asset_name' in asset else None - - params['end_daily'] = pd.to_datetime( - asset['end_daily'], utc=True - ) if 'end_daily' in asset and asset['end_daily'] != 'N/A' \ - else None - - params['end_minute'] = pd.to_datetime( - asset['end_minute'], utc=True - ) if 'end_minute' in asset and asset['end_minute'] != 'N/A' \ - else None - - else: - params['symbol'] = self.get_catalyst_symbol(market) - - trading_pair = TradingPair(**params) - self.assets[market['id']] = trading_pair + for asset_def in asset_defs: + if asset_def[0] is not None or not asset_defs[1]: + try: + asset = self.create_trading_pair( + market=market, + asset_def=asset_def[0], + is_local=asset_def[1] + ) + self.assets.append(asset) + except TypeError as e: + pass def get_balances(self): try: @@ -293,7 +331,7 @@ class CCXT(Exchange): order = Order( dt=date, - asset=self.assets[symbol], + asset=self.get_asset(symbol, is_exchange_symbol=True), amount=amount, stop=stop_price, limit=limit_price, diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index 317175d0..f38bfa36 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -34,7 +34,8 @@ class Exchange: def __init__(self): self.name = None - self.assets = dict() + self.assets = [] + self._symbol_maps = [None, None] self._portfolio = None self.minute_writer = None self.minute_reader = None @@ -145,9 +146,9 @@ class Exchange: """ symbol = None - for key in self.assets: - if not symbol and self.assets[key].symbol == asset.symbol: - symbol = key + for a in self.assets: + if not symbol and a.symbol == asset.symbol: + symbol = a.symbol if not symbol: raise ValueError('Currency %s not supported by exchange %s' % @@ -187,33 +188,32 @@ class Exchange: list[TradingPair] """ - assets = [] if symbols is not None: + assets = [] for symbol in symbols: asset = self.get_asset(symbol, data_frequency) assets.append(asset) + return assets else: - for key in self.assets: - assets.append(self.assets[key]) + return self.assets - return assets - - def _find_asset(self, asset, symbol, data_frequency, is_local=False): - assets = self.assets - for key in assets: + def _find_asset(self, asset, symbol, data_frequency, is_exchange_symbol, + is_local=False): + for a in self.assets: has_data = (data_frequency == 'minute' - and assets[key].end_minute is not None) \ + and a.end_minute is not None) \ or (data_frequency == 'daily' - and assets[key].end_daily is not None) + and a.end_daily is not None) - if not asset and assets[key].symbol.lower() == symbol.lower() \ + symbol_attr = a.exchange_symbol if is_exchange_symbol else a.symbol + if not asset and symbol_attr.lower() == symbol.lower() \ and (not data_frequency or has_data): - asset = assets[key] + asset = a return asset - def get_asset(self, symbol, data_frequency=None): + def get_asset(self, symbol, data_frequency=None, is_exchange_symbol=False): """ The market for the specified symbol. @@ -229,16 +229,19 @@ class Exchange: asset = None log.debug('searching asset {} on the server'.format(symbol)) - asset = self._find_asset(asset, symbol, data_frequency, False) + asset = self._find_asset( + asset, symbol, data_frequency, is_exchange_symbol, False + ) log.debug('asset {} not found on the server, searching local ' 'assets'.format(symbol)) - asset = self._find_asset(asset, symbol, data_frequency, True) + asset = self._find_asset( + asset, symbol, data_frequency, is_exchange_symbol, True + ) if not asset: - all_values = list(self.assets.values()) supported_symbols = sorted([ - asset.symbol for asset in all_values + asset.symbol for asset in self.assets ]) raise SymbolNotFoundOnExchange( @@ -250,7 +253,14 @@ class Exchange: return asset def fetch_symbol_map(self, is_local=False): - return get_exchange_symbols(self.name, is_local) + index = 1 if is_local else 0 + if self._symbol_maps[index] is not None: + return self._symbol_maps[index] + + else: + symbol_map = get_exchange_symbols(self.name, is_local) + self._symbol_maps[index] = symbol_map + return symbol_map @abstractmethod def load_assets(self, is_local=False): diff --git a/catalyst/exchange/exchange_bundle.py b/catalyst/exchange/exchange_bundle.py index 2bfa6201..4f4e7fc6 100644 --- a/catalyst/exchange/exchange_bundle.py +++ b/catalyst/exchange/exchange_bundle.py @@ -31,7 +31,7 @@ from catalyst.exchange.exchange_errors import EmptyValuesInBundleError, \ PricingDataNotLoadedError, DataCorruptionError, ExchangeSymbolsNotFound, \ PricingDataValueError from catalyst.exchange.exchange_utils import get_exchange_folder, \ - get_exchange_symbols, save_exchange_symbols + get_exchange_symbols, save_exchange_symbols, mixin_market_params from catalyst.utils.cli import maybe_show_progress from catalyst.utils.paths import ensure_directory @@ -667,12 +667,11 @@ class ExchangeBundle: """ log.info('ingesting csv file: {}'.format(path)) - try: - symbols_def = get_exchange_symbols( - self.exchange_name, is_local=True - ) - except ExchangeSymbolsNotFound: - symbols_def = dict() + + if self.exchange is None: + # Avoid circular dependencies + from catalyst.exchange.factory import get_exchange + self.exchange = get_exchange(self.exchange_name) problems = [] df = pd.read_csv( @@ -705,24 +704,40 @@ class ExchangeBundle: end_dt = df.index.get_level_values(1).max() end_dt_key = 'end_{}'.format(data_frequency) - if symbol is symbols_def: - symbol_def = symbols_def[symbol] + market = self.exchange.get_market(symbol) + if market is None: + raise ValueError('symbol not available in the exchange.') - start_dt = symbol_def['start_date'] \ - if symbol_def['start_date'] < start_dt else start_dt + params = dict( + exchange=self.exchange.name, + data_source='local', + exchange_symbol=market['id'], + ) + mixin_market_params(self.exchange_name, params, market) - end_dt = symbol_def[end_dt_key] \ - if symbol_def[end_dt_key] > end_dt else end_dt + asset_def = self.exchange.fetch_asset_def(market, True) + if asset_def is not None: + params['symbol'] = asset_def['symbol'] - end_daily = end_dt \ - if data_frequency == 'daily' else symbol_def['end_daily'] + params['start_date'] = asset_def['start_date'] \ + if asset_def['start_date'] < start_dt else start_dt - end_minute = end_dt \ - if data_frequency == 'minute' else symbol_def['end_minute'] + params['end_date'] = asset_def[end_dt_key] \ + if asset_def[end_dt_key] > end_dt else end_dt + + params['end_daily'] = end_dt \ + if data_frequency == 'daily' else asset_def['end_daily'] + + params['end_minute'] = end_dt \ + if data_frequency == 'minute' else asset_def['end_minute'] else: - end_daily = end_dt if data_frequency == 'daily' else 'N/A' - end_minute = end_dt if data_frequency == 'minute' else 'N/A' + params['symbol'] = self.exchange.get_catalyst_symbol(market) + + params['end_daily'] = end_dt \ + if data_frequency == 'daily' else 'N/A' + params['end_minute'] = end_dt \ + if data_frequency == 'minute' else 'N/A' if min_start_dt is None or start_dt < min_start_dt: min_start_dt = start_dt @@ -730,19 +745,8 @@ class ExchangeBundle: if max_end_dt is None or end_dt > max_end_dt: max_end_dt = end_dt - asset = TradingPair( - symbol=symbol, - exchange=self.exchange_name, - start_date=start_dt, - end_date=end_dt, - leverage=0, # TODO: add as an optional column - asset_name=symbol, - min_trade_size=0, # TODO: add as an optional column - end_daily=end_daily, - end_minute=end_minute, - exchange_symbol=symbol - ) - assets[symbol] = asset + asset = TradingPair(**params) + assets[market['id']] = asset save_exchange_symbols(self.exchange_name, assets, True) diff --git a/catalyst/exchange/exchange_utils.py b/catalyst/exchange/exchange_utils.py index 0ca18c95..c2250187 100644 --- a/catalyst/exchange/exchange_utils.py +++ b/catalyst/exchange/exchange_utils.py @@ -8,6 +8,7 @@ from datetime import date, datetime import pandas as pd from catalyst.assets._assets import TradingPair +from six import string_types from six.moves.urllib import request from catalyst.constants import DATE_FORMAT, SYMBOLS_URL @@ -100,6 +101,20 @@ def download_exchange_symbols(exchange_name, environ=None): return response +def symbols_parser(asset_def): + for key, value in asset_def.items(): + match = isinstance(value, string_types) \ + and re.search(r'(\d{4}-\d{2}-\d{2})', value) + + if match: + try: + asset_def[key] = pd.to_datetime(value, utc=True) + except ValueError: + pass + + return asset_def + + def get_exchange_symbols(exchange_name, is_local=False, environ=None): """ The de-serialized content of the exchange's symbols.json. @@ -125,10 +140,10 @@ def get_exchange_symbols(exchange_name, is_local=False, environ=None): if os.path.isfile(filename): with open(filename) as data_file: try: - data = json.load(data_file) + data = json.load(data_file, object_hook=symbols_parser) return data - except ValueError: + except ValueError as e: return dict() else: raise ExchangeSymbolsNotFound( From f995f451a7d5158d50774d598ae57574fe6a3be9 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Sat, 2 Dec 2017 21:27:03 -0500 Subject: [PATCH 14/52] BLD: some refactoring to simplify the integration logic and tested several algos --- catalyst/assets/_assets.pyx | 6 +- catalyst/exchange/ccxt/ccxt_exchange.py | 88 +++++++++++++++++++-- catalyst/exchange/exchange.py | 100 ++++++++++++++++-------- catalyst/exchange/exchange_bundle.py | 2 +- 4 files changed, 153 insertions(+), 43 deletions(-) diff --git a/catalyst/assets/_assets.pyx b/catalyst/assets/_assets.pyx index feb41710..af1e81a6 100644 --- a/catalyst/assets/_assets.pyx +++ b/catalyst/assets/_assets.pyx @@ -516,7 +516,7 @@ cdef class TradingPair(Asset): symbol = symbol.lower() try: - self.base_currency,self.quote_currency = symbol.split('_') + self.base_currency, self.quote_currency = symbol.split('_') except Exception as e: raise InvalidSymbolError(symbol=symbol, error=e) @@ -600,8 +600,8 @@ cdef class TradingPair(Asset): ------- boolean: whether the asset's exchange is open at the given minute. """ - #TODO: consider implementing to spot holds - return self.trading_state > 0 + #TODO: make more dymanic to catch holds + return True cpdef __reduce__(self): """ diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py index 4d5b3939..ac978e58 100644 --- a/catalyst/exchange/ccxt/ccxt_exchange.py +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -84,6 +84,19 @@ class CCXT(Exchange): return None def get_market(self, symbol): + """ + The CCXT market. + + Parameters + ---------- + symbol: + The CCXT symbol. + + Returns + ------- + dict[str, Object] + + """ s = self.get_symbol(symbol) market = next( (market for market in self.markets if market['symbol'] == s), @@ -92,6 +105,17 @@ class CCXT(Exchange): return market def get_symbol(self, asset_or_symbol): + """ + The CCXT symbol. + + Parameters + ---------- + asset_or_symbol + + Returns + ------- + + """ symbol = asset_or_symbol if isinstance( asset_or_symbol, string_types ) else asset_or_symbol.symbol @@ -100,6 +124,17 @@ class CCXT(Exchange): return '{}/{}'.format(parts[0].upper(), parts[1].upper()) def get_catalyst_symbol(self, market_or_symbol): + """ + The Catalyst symbol. + + Parameters + ---------- + market_or_symbol + + Returns + ------- + + """ if isinstance(market_or_symbol, string_types): parts = market_or_symbol.split('/') return '{}_{}'.format(parts[0].lower(), parts[1].lower()) @@ -111,6 +146,19 @@ class CCXT(Exchange): ) def get_timeframe(self, freq): + """ + The CCXT timeframe from the Catalyst frequency. + + Parameters + ---------- + freq: str + The Catalyst frequency (Pandas convention) + + Returns + ------- + str + + """ freq_match = re.match(r'([0-9].*)?(m|M|d|D|h|H|T)', freq, re.M | re.I) if freq_match: candle_size = int(freq_match.group(1)) \ @@ -168,16 +216,47 @@ class CCXT(Exchange): except ExchangeSymbolsNotFound: return None - def fetch_asset_defs(self, market): + def get_asset_defs(self, market): + """ + The local and Catalyst definitions of the specified market. + + Parameters + ---------- + market: dict[str, Object] + The CCXT market dicts. + + Returns + ------- + dict[str, Object] + The asset definition. + + """ asset_defs = [] for is_local in (False, True): - asset_def = self.fetch_asset_def(market, is_local) + asset_def = self.get_asset_def(market, is_local) asset_defs.append((asset_def, is_local)) return asset_defs - def fetch_asset_def(self, market, is_local=False): + def get_asset_def(self, market, is_local=False): + """ + The asset definition (in symbols.json files) corresponding + to the the specified market. + + Parameters + ---------- + market: dict[str, Object] + The CCXT market dict. + is_local + Whether to search in local or Catalyst asset definitions. + + Returns + ------- + dict[str, Object] + The asset definition. + + """ exchange_symbol = market['id'] symbol_map = self._fetch_symbol_map(is_local) @@ -250,8 +329,7 @@ class CCXT(Exchange): self.assets = [] for market in self.markets: - log.debug('fetching asset for market: {}'.format(market['id'])) - asset_defs = self.fetch_asset_defs(market) + asset_defs = self.get_asset_defs(market) for asset_def in asset_defs: if asset_def[0] is not None or not asset_defs[1]: diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index f38bfa36..0301ebc0 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -175,71 +175,102 @@ class Exchange: return symbols - def get_assets(self, symbols=None, data_frequency=None): + def get_assets(self, symbols=None, data_frequency=None, + is_exchange_symbol=False, + is_local=None): """ The list of markets for the specified symbols. Parameters ---------- symbols: list[str] + data_frequency: str + is_exchange_symbol: bool + is_local: bool Returns ------- list[TradingPair] + A list of asset objects. + + Notes + ----- + See get_asset for details of each parameter. """ + if symbols is None: + # Make a distinct list of all symbols + symbols = list(set([asset.symbol for asset in self.assets])) + is_exchange_symbol = False - if symbols is not None: - assets = [] - for symbol in symbols: - asset = self.get_asset(symbol, data_frequency) - assets.append(asset) - return assets - else: - return self.assets + assets = [] + for symbol in symbols: + asset = self.get_asset( + symbol, data_frequency, is_exchange_symbol, is_local + ) + assets.append(asset) + return assets - def _find_asset(self, asset, symbol, data_frequency, is_exchange_symbol, - is_local=False): - for a in self.assets: - has_data = (data_frequency == 'minute' - and a.end_minute is not None) \ - or (data_frequency == 'daily' - and a.end_daily is not None) - - symbol_attr = a.exchange_symbol if is_exchange_symbol else a.symbol - if not asset and symbol_attr.lower() == symbol.lower() \ - and (not data_frequency or has_data): - asset = a - - return asset - - def get_asset(self, symbol, data_frequency=None, is_exchange_symbol=False): + def get_asset(self, symbol, data_frequency=None, is_exchange_symbol=False, + is_local=None): """ The market for the specified symbol. Parameters ---------- symbol: str + The Catalyst or exchange symbol. + + data_frequency: str + Check for asset corresponding to the specified data_frequency. + The same asset might exist in the Catalyst repository or + locally (following a CSV ingestion). Filtering by + data_frequency picks the right asset. + + is_exchange_symbol: bool + Whether the symbol uses the Catalyst or exchange convention. + + is_local: bool + For the local or Catalyst asset. Returns ------- TradingPair + The asset object. """ asset = None - log.debug('searching asset {} on the server'.format(symbol)) - asset = self._find_asset( - asset, symbol, data_frequency, is_exchange_symbol, False + log.debug( + 'searching assets for: {} {}'.format( + self.name, symbol + ) ) + for a in self.assets: + if asset is not None: + break - log.debug('asset {} not found on the server, searching local ' - 'assets'.format(symbol)) - asset = self._find_asset( - asset, symbol, data_frequency, is_exchange_symbol, True - ) + if is_local is not None: + data_source = 'local' if is_local else 'catalyst' + applies = (a.data_source == data_source) + + elif data_frequency is not None: + applies = ( + (data_frequency == 'minute' and a.end_minute is not None) + or (data_frequency == 'daily' and a.end_daily is not None) + ) + + else: + applies = True + + # The symbol provided may use the Catalyst or the exchange + # convention + key = a.exchange_symbol if is_exchange_symbol else a.symbol + if not asset and key.lower() == symbol.lower() and applies: + asset = a + + if asset is None: - if not asset: supported_symbols = sorted([ asset.symbol for asset in self.assets ]) @@ -250,6 +281,7 @@ class Exchange: supported_symbols=supported_symbols ) + log.debug('found asset: {}'.format(asset)) return asset def fetch_symbol_map(self, is_local=False): diff --git a/catalyst/exchange/exchange_bundle.py b/catalyst/exchange/exchange_bundle.py index 4f4e7fc6..07bc0207 100644 --- a/catalyst/exchange/exchange_bundle.py +++ b/catalyst/exchange/exchange_bundle.py @@ -715,7 +715,7 @@ class ExchangeBundle: ) mixin_market_params(self.exchange_name, params, market) - asset_def = self.exchange.fetch_asset_def(market, True) + asset_def = self.exchange.get_asset_def(market, True) if asset_def is not None: params['symbol'] = asset_def['symbol'] From 96a27d083c6c589819fa0311345def8a9548f1c8 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Sun, 3 Dec 2017 00:04:15 -0500 Subject: [PATCH 15/52] BLD: more live trading tests and fixed related issues --- catalyst/examples/mean_reversion_simple.py | 8 ++++---- catalyst/exchange/ccxt/ccxt_exchange.py | 23 +++++++++++++++------- catalyst/exchange/exchange.py | 4 ++-- catalyst/exchange/exchange_bundle.py | 6 ++---- catalyst/exchange/stats_utils.py | 6 +++--- 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/catalyst/examples/mean_reversion_simple.py b/catalyst/examples/mean_reversion_simple.py index cd1ac8dc..130343c9 100644 --- a/catalyst/examples/mean_reversion_simple.py +++ b/catalyst/examples/mean_reversion_simple.py @@ -37,7 +37,7 @@ def initialize(context): context.base_price = None context.current_day = None - context.RSI_OVERSOLD = 25 + context.RSI_OVERSOLD = 55 context.RSI_OVERBOUGHT = 82 context.CANDLE_SIZE = '5T' @@ -239,7 +239,7 @@ def analyze(context=None, perf=None): if __name__ == '__main__': # The execution mode: backtest or live - MODE = 'backtest' + MODE = 'live' if MODE == 'backtest': folder = os.path.join( @@ -251,7 +251,7 @@ if __name__ == '__main__': out = os.path.join(folder, '{}.p'.format(timestr)) # catalyst run -f catalyst/examples/mean_reversion_simple.py -x bitfinex -s 2017-10-1 -e 2017-11-10 -c usdt -n mean-reversion --data-frequency minute --capital-base 10000 run_algorithm( - capital_base=0.5, + capital_base=0.1, data_frequency='minute', initialize=initialize, handle_data=handle_data, @@ -267,7 +267,7 @@ if __name__ == '__main__': elif MODE == 'live': run_algorithm( - capital_base=0.5, + capital_base=0.1, initialize=initialize, handle_data=handle_data, analyze=analyze, diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py index ac978e58..36a0bbdb 100644 --- a/catalyst/exchange/ccxt/ccxt_exchange.py +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -321,6 +321,7 @@ class CCXT(Exchange): else: params['symbol'] = self.get_catalyst_symbol(market) + # TODO: add as an optional column params['leverage'] = 1.0 return TradingPair(**params) @@ -340,7 +341,8 @@ class CCXT(Exchange): is_local=asset_def[1] ) self.assets.append(asset) - except TypeError as e: + + except TypeError: pass def get_balances(self): @@ -405,7 +407,11 @@ class CCXT(Exchange): # order_id = str(order_status['info']['clientOrderId']) order_id = order_status['id'] - symbol = order_status['info']['symbol'] + + # TODO: this won't work, redo the packages with a different key. + symbol = order_status['info']['symbol'] \ + if 'symbol' in order_status['info'] \ + else order_status['info']['Exchange'] order = Order( dt=date, @@ -455,10 +461,9 @@ class CCXT(Exchange): if 'info' not in result: raise ValueError('cannot use order without info attribute') - # order_id = str(result['info']['clientOrderId']) order_id = result['id'] order = Order( - dt=from_ms_timestamp(result['info']['transactTime']), + dt=pd.Timestamp.utcnow(), asset=asset, amount=amount, stop=style.get_stop_price(is_buy), @@ -490,7 +495,7 @@ class CCXT(Exchange): def _get_asset_from_order(self, order_id): open_orders = self.portfolio.open_orders order = next( - (order for order in open_orders if order.id == order_id), + (open_orders[id] for id in open_orders if id == order_id), None ) # type: Order return order.asset if order is not None else None @@ -508,12 +513,12 @@ class CCXT(Exchange): symbol = self.get_symbol(asset_or_symbol) \ if asset_or_symbol is not None else None order_status = self.api.fetch_order(id=order_id, symbol=symbol) - order, _ = self._create_order(order_status) + order, executed_price = self._create_order(order_status) except Exception as e: raise ExchangeRequestError(error=e) - return order + return order, executed_price def cancel_order(self, order_param, asset_or_symbol=None): order_id = order_param.id \ @@ -555,6 +560,10 @@ class CCXT(Exchange): ticker['last_traded'] = from_ms_timestamp(ticker['timestamp']) + if 'last_price' not in ticker: + # TODO: any more exceptions? + ticker['last_price'] = ticker['last'] + # Using the volume represented in the base currency ticker['volume'] = ticker['baseVolume'] \ if 'baseVolume' in ticker else 0 diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index 0301ebc0..a8d58069 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -270,7 +270,6 @@ class Exchange: asset = a if asset is None: - supported_symbols = sorted([ asset.symbol for asset in self.assets ]) @@ -704,8 +703,9 @@ class Exchange: # TODO: convert if the position is not in the base currency ticker = tickers[asset] position = portfolio.positions[asset] + position.last_sale_price = ticker['last_price'] - position.last_sale_date = ticker['timestamp'] + position.last_sale_date = ticker['last_traded'] portfolio.positions_value += \ position.amount * position.last_sale_price diff --git a/catalyst/exchange/exchange_bundle.py b/catalyst/exchange/exchange_bundle.py index 07bc0207..59e5ce90 100644 --- a/catalyst/exchange/exchange_bundle.py +++ b/catalyst/exchange/exchange_bundle.py @@ -1,5 +1,4 @@ import os -import os import shutil from datetime import datetime, timedelta from functools import partial @@ -28,10 +27,9 @@ from catalyst.exchange.exchange_bcolz import BcolzExchangeBarReader, \ from catalyst.exchange.exchange_errors import EmptyValuesInBundleError, \ TempBundleNotFoundError, \ NoDataAvailableOnExchange, \ - PricingDataNotLoadedError, DataCorruptionError, ExchangeSymbolsNotFound, \ - PricingDataValueError + PricingDataNotLoadedError, DataCorruptionError, PricingDataValueError from catalyst.exchange.exchange_utils import get_exchange_folder, \ - get_exchange_symbols, save_exchange_symbols, mixin_market_params + save_exchange_symbols, mixin_market_params from catalyst.utils.cli import maybe_show_progress from catalyst.utils.paths import ensure_directory diff --git a/catalyst/exchange/stats_utils.py b/catalyst/exchange/stats_utils.py index e982de2a..845fccc8 100644 --- a/catalyst/exchange/stats_utils.py +++ b/catalyst/exchange/stats_utils.py @@ -153,11 +153,11 @@ def get_pretty_stats(stats_df, recorded_cols=None, num_rows=10): def format_positions(positions): parts = [] for position in positions: - msg = '{amount:.2f}{market} cost basis {cost_basis:.4f}{base}'.format( + msg = '{amount:.2f}{base} cost basis {cost_basis:.4f}{quote}'.format( amount=position['amount'], - market=position['sid'].market_currency, + base=position['sid'].base_currency, cost_basis=position['cost_basis'], - base=position['sid'].base_currency + quote=position['sid'].quote_currency ) parts.append(msg) return ', '.join(parts) From dcdf4f77db036ac36db68e54a883e3c034420b1a Mon Sep 17 00:00:00 2001 From: fredfortier Date: Tue, 5 Dec 2017 22:08:30 -0500 Subject: [PATCH 16/52] BLD: making some adjustment to the blotter to improve paper trading --- catalyst/examples/mean_reversion_simple.py | 5 +- catalyst/exchange/ccxt/ccxt_exchange.py | 56 +++--- catalyst/exchange/exchange.py | 9 +- catalyst/exchange/exchange_algorithm.py | 192 +++++++++++---------- catalyst/exchange/exchange_blotter.py | 46 +++++ catalyst/exchange/exchange_errors.py | 7 + catalyst/exchange/exchange_portfolio.py | 23 ++- catalyst/exchange/exchange_utils.py | 4 + etc/requirements.txt | 2 +- 9 files changed, 218 insertions(+), 126 deletions(-) diff --git a/catalyst/examples/mean_reversion_simple.py b/catalyst/examples/mean_reversion_simple.py index 130343c9..21e0c7cc 100644 --- a/catalyst/examples/mean_reversion_simple.py +++ b/catalyst/examples/mean_reversion_simple.py @@ -256,7 +256,7 @@ if __name__ == '__main__': initialize=initialize, handle_data=handle_data, analyze=analyze, - exchange_name='bitfinex', + exchange_name='bittrex', algo_namespace=NAMESPACE, base_currency='eth', start=pd.to_datetime('2017-10-01', utc=True), @@ -275,5 +275,6 @@ if __name__ == '__main__': live=True, algo_namespace=NAMESPACE, base_currency='eth', - live_graph=False + live_graph=False, + simulate_orders=True ) diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py index 36a0bbdb..7f45627f 100644 --- a/catalyst/exchange/ccxt/ccxt_exchange.py +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -3,6 +3,7 @@ from collections import defaultdict import ccxt import pandas as pd +import six from ccxt import ExchangeNotAvailable from six import string_types @@ -19,7 +20,7 @@ from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \ ExchangeSymbolsNotFound, ExchangeRequestError, InvalidOrderStyle, \ ExchangeNotFoundError from catalyst.exchange.exchange_utils import mixin_market_params, \ - from_ms_timestamp + from_ms_timestamp, get_epoch log = Logger('CCXT', level=LOG_LEVEL) @@ -182,33 +183,48 @@ class CCXT(Exchange): def get_candles(self, freq, assets, bar_count=None, start_dt=None, end_dt=None): + is_single = (isinstance(assets, TradingPair)) + if is_single: + assets = [assets] + symbols = self.get_symbols(assets) + timeframe = self.get_timeframe(freq) - delta = start_dt - pd.to_datetime('1970-1-1', utc=True) + delta = start_dt - get_epoch() ms = int(delta.total_seconds()) * 1000 candles = dict() for asset in assets: - ohlcvs = self.api.fetch_ohlcv( - symbol=symbols[0], - timeframe=timeframe, - since=ms, - limit=bar_count, - params={} - ) + try: + ohlcvs = self.api.fetch_ohlcv( + symbol=symbols[0], + timeframe=timeframe, + since=ms, + limit=bar_count, + params={} + ) - candles[asset] = [] - for ohlcv in ohlcvs: - candles[asset].append(dict( - last_traded=pd.to_datetime(ohlcv[0], unit='ms', utc=True), - open=ohlcv[1], - high=ohlcv[2], - low=ohlcv[3], - close=ohlcv[4], - volume=ohlcv[5] - )) + candles[asset] = [] + for ohlcv in ohlcvs: + candles[asset].append(dict( + last_traded=pd.to_datetime( + ohlcv[0], unit='ms', utc=True + ), + open=ohlcv[1], + high=ohlcv[2], + low=ohlcv[3], + close=ohlcv[4], + volume=ohlcv[5] + )) - return candles + except Exception as e: + raise ExchangeRequestError(error=e) + + if is_single: + return six.next(six.itervalues(candles)) + + else: + return candles def _fetch_symbol_map(self, is_local): try: diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index a8d58069..ea938387 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -324,7 +324,6 @@ class Exchange: list[Transaction] """ - transactions = list() if self.portfolio.open_orders: for order_id in list(self.portfolio.open_orders): log.debug('found open order: {}'.format(order_id)) @@ -344,12 +343,13 @@ class Exchange: order_id=order.id, commission=order.commission ) - transactions.append(transaction) + yield order, transaction - self.portfolio.execute_order(order, transaction) + # self.portfolio.execute_order(order, transaction) elif order.status == ORDER_STATUS.CANCELLED: - self.portfolio.remove_order(order) + # self.portfolio.remove_order(order) + yield order, None else: delta = pd.Timestamp.utcnow() - order.dt @@ -359,7 +359,6 @@ class Exchange: delta=delta ) ) - return transactions def get_spot_value(self, assets, field, dt=None, data_frequency='minute'): """ diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index c2102015..7f671c96 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -32,7 +32,7 @@ from catalyst.exchange.exchange_errors import ( ExchangeRequestError, ExchangePortfolioDataError, ExchangeTransactionError, - OrphanOrderError) + OrphanOrderError, OrderTypeNotSupported) from catalyst.exchange.exchange_execution import ExchangeStopLimitOrder, \ ExchangeLimitOrder, ExchangeStopOrder from catalyst.exchange.exchange_utils import save_algo_object, get_algo_object, \ @@ -63,9 +63,72 @@ class ExchangeAlgorithmExecutor(AlgorithmSimulator): class ExchangeTradingAlgorithmBase(TradingAlgorithm): def __init__(self, *args, **kwargs): self.exchanges = kwargs.pop('exchanges', None) + self.simulate_orders = kwargs.pop('simulate_orders', None) super(ExchangeTradingAlgorithmBase, self).__init__(*args, **kwargs) + if self.simulate_orders is None \ + and self.sim_params.arena == 'backtest': + self.simulate_orders = True + + self.blotter = ExchangeBlotter( + data_frequency=self.data_frequency, + # Default to NeverCancel in catalyst + cancel_policy=self.cancel_policy, + simulate_orders=self.simulate_orders + ) + + @staticmethod + def __convert_order_params_for_blotter(limit_price, stop_price, style): + """ + Helper method for converting deprecated limit_price and stop_price + arguments into ExecutionStyle instances. + + This function assumes that either style == None or (limit_price, + stop_price) == (None, None). + """ + if stop_price: + raise OrderTypeNotSupported(order_type='stop') + + if style: + if limit_price is not None: + raise ValueError( + 'An order style and a limit price was included in the ' + 'order. Please pick one to avoid any possible conflict.' + ) + + # Currently limiting order types or limit and market to + # be in-line with CXXT and many exchanges. We'll consider + # adding more order types in the future. + if not isinstance(style, ExchangeLimitOrder) or \ + not isinstance(style, MarketOrder): + raise OrderTypeNotSupported( + order_type=style.__class__.__name__ + ) + + return style + + if limit_price: + return ExchangeLimitOrder(limit_price) + else: + return MarketOrder() + + def _calculate_order(self, asset, amount, + limit_price=None, stop_price=None, style=None): + # Raises a ZiplineError if invalid parameters are detected. + self.validate_order_params(asset, + amount, + limit_price, + stop_price, + style) + + # Convert deprecated limit_price and stop_price parameters to use + # ExecutionStyle objects. + style = self.__convert_order_params_for_blotter(limit_price, + stop_price, + style) + return amount, style + def round_order(self, amount, asset): """ We need fractions with cryptocurrencies @@ -204,50 +267,8 @@ class ExchangeTradingAlgorithmBacktest(ExchangeTradingAlgorithmBase): super(ExchangeTradingAlgorithmBacktest, self).__init__(*args, **kwargs) self.frame_stats = list() - self.blotter = ExchangeBlotter( - data_frequency=self.data_frequency, - # Default to NeverCancel in catalyst - cancel_policy=self.cancel_policy, - ) log.info('initialized trading algorithm in backtest mode') - def _calculate_order(self, asset, amount, - limit_price=None, stop_price=None, style=None): - # Raises a ZiplineError if invalid parameters are detected. - self.validate_order_params(asset, - amount, - limit_price, - stop_price, - style) - - # Convert deprecated limit_price and stop_price parameters to use - # ExecutionStyle objects. - style = self.__convert_order_params_for_blotter(limit_price, - stop_price, - style) - return amount, style - - @staticmethod - def __convert_order_params_for_blotter(limit_price, stop_price, style): - """ - Helper method for converting deprecated limit_price and stop_price - arguments into ExecutionStyle instances. - - This function assumes that either style == None or (limit_price, - stop_price) == (None, None). - """ - if style: - assert (limit_price, stop_price) == (None, None) - return style - if limit_price and stop_price: - return ExchangeStopLimitOrder(limit_price, stop_price) - if limit_price: - return ExchangeLimitOrder(limit_price) - if stop_price: - return ExchangeStopOrder(stop_price) - else: - return MarketOrder() - def is_last_frame_of_day(self, data): # TODO: adjust here to support more intervals next_frame_dt = data.current_dt + timedelta(minutes=1) @@ -289,7 +310,6 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): def __init__(self, *args, **kwargs): self.algo_namespace = kwargs.pop('algo_namespace', None) self.live_graph = kwargs.pop('live_graph', None) - self.simulate_orders = kwargs.pop('simulate_orders', None) self._clock = None self.frame_stats = deque(maxlen=60) @@ -416,16 +436,6 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): return self.trading_client.transform() def updated_portfolio(self): - """ - We skip the entire performance tracker business and update the - portfolio directly. - - Returns - ------- - ExchangePortfolio - - """ - # TODO: build cumulative portfolio return self.perf_tracker.get_portfolio(False) def updated_account(self): @@ -450,6 +460,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): last_sale_date=position.last_sale_date, last_sale_price=position.last_sale_price ) + except ExchangeRequestError as e: log.warn( 'update portfolio attempt {}: {}'.format(attempt_index, e) @@ -464,30 +475,6 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): error=e ) - def _check_open_orders(self, attempt_index=0): - try: - orders = list() - for exchange_name in self.exchanges: - exchange = self.exchanges[exchange_name] - exchange_orders = exchange.check_open_orders() - - orders += exchange_orders - - return orders - except ExchangeRequestError as e: - log.warn( - 'check open orders attempt {}: {}'.format(attempt_index, e) - ) - if attempt_index < self.retry_check_open_orders: - sleep(self.retry_delay) - return self._check_open_orders(attempt_index + 1) - else: - raise ExchangePortfolioDataError( - data_type='order-status', - attempts=attempt_index, - error=e - ) - def add_pnl_stats(self, period_stats): """ Save p&l stats. @@ -577,14 +564,19 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): if not self.is_running: return - self._synchronize_portfolio() + new_transactions, new_commissions, closed_orders = \ + self.blotter.get_transactions(data) - transactions = self._check_open_orders() - if len(transactions) > 0: - for transaction in transactions: - self.perf_tracker.process_transaction(transaction) + self.blotter.prune_orders(closed_orders) - self.perf_tracker.update_performance() + for transaction in new_transactions: + self.perf_tracker.process_transaction(transaction) + + # since this order was modified, record it + order = self.blotter.orders[transaction.order_id] + self.perf_tracker.process_order(order) + + self.perf_tracker.update_performance() if self._handle_data: self._handle_data(self, data) @@ -713,25 +705,35 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): order: Order The catalyst order object or None """ - amount, style = self._calculate_order(asset, amount, - limit_price, stop_price, - style) - order_id = self._order(asset, amount, limit_price, stop_price, style) + if self.simulate_orders: + order_id = super(ExchangeTradingAlgorithmLive, self).order( + asset, amount, limit_price, stop_price, style + ) + log.debug('created a simulated order {}'.format(order_id)) + + else: + amount, style = self._calculate_order( + asset, amount, limit_price, stop_price, style + ) + order_id = self._order( + asset, amount, limit_price, stop_price, style + ) - exchange = self.exchanges[asset.exchange] - exchange_portfolio = exchange.portfolio if order_id is not None: + current_order = None + for order in self.blotter.open_orders[asset]: + if current_order is None and order.id == order_id: + self.perf_tracker.process_order(order) + current_order = order - if order_id in exchange_portfolio.open_orders: - order = exchange_portfolio.open_orders[order_id] - self.perf_tracker.process_order(order) - return order + if current_order is not None: + return current_order else: raise OrphanOrderError( order_id=order_id, - exchange=exchange.name + exchange=asset.exchange ) else: log.warn('unable to order {} {} on exchange {}'.format( diff --git a/catalyst/exchange/exchange_blotter.py b/catalyst/exchange/exchange_blotter.py index 365682c7..72e6d539 100644 --- a/catalyst/exchange/exchange_blotter.py +++ b/catalyst/exchange/exchange_blotter.py @@ -1,7 +1,11 @@ +from time import sleep + from catalyst.assets._assets import TradingPair from logbook import Logger from catalyst.constants import LOG_LEVEL +from catalyst.exchange.exchange_errors import ExchangeRequestError, \ + ExchangePortfolioDataError from catalyst.finance.blotter import Blotter from catalyst.finance.commission import CommissionModel from catalyst.finance.slippage import SlippageModel @@ -121,6 +125,8 @@ class TradingPairFixedSlippage(SlippageModel): class ExchangeBlotter(Blotter): def __init__(self, *args, **kwargs): + self.simulate_orders = kwargs.pop('simulate_orders', False) + super(ExchangeBlotter, self).__init__(*args, **kwargs) # Using the equity models for now @@ -132,3 +138,43 @@ class ExchangeBlotter(Blotter): self.commission_models = { TradingPair: TradingPairFeeSchedule() } + + def get_exchange_transactions(self, attempt_index=0): + closed_orders = [] + transactions = [] + commissions = [] + + try: + for exchange_name in self.exchanges: + exchange = self.exchanges[exchange_name] + for order, txn in exchange.check_open_orders(): + + order.dt = txn.dt + + transactions.append(txn) + + if not order.open: + closed_orders.append(order) + + return transactions, commissions, closed_orders + + except ExchangeRequestError as e: + log.warn( + 'check open orders attempt {}: {}'.format(attempt_index, e) + ) + if attempt_index < self.retry_check_open_orders: + sleep(self.retry_delay) + return self.get_exchange_transactions(attempt_index + 1) + else: + raise ExchangePortfolioDataError( + data_type='order-status', + attempts=attempt_index, + error=e + ) + + def get_transactions(self, bar_data): + if self.simulate_orders: + return super(ExchangeBlotter, self).get_transactions(bar_data) + + else: + return self.get_exchange_transactions() diff --git a/catalyst/exchange/exchange_errors.py b/catalyst/exchange/exchange_errors.py index 5530ccb2..7bf66f6c 100644 --- a/catalyst/exchange/exchange_errors.py +++ b/catalyst/exchange/exchange_errors.py @@ -217,6 +217,7 @@ class PricingDataNotLoadedError(ZiplineError): '{data_frequency} -i {symbol_list}`. See catalyst documentation ' 'for details.').strip() + class PricingDataValueError(ZiplineError): msg = ('Unable to retrieve pricing data for {exchange} {symbol} ' '[{start_dt} - {end_dt}]: {error}').strip() @@ -244,3 +245,9 @@ class NoDataAvailableOnExchange(ZiplineError): class NoValueForField(ZiplineError): msg = ('Value not found for field: {field}.').strip() + + +class OrderTypeNotSupported(ZiplineError): + msg = ( + 'Order type `{order_type}` not currencly supported by Catalyst. ' + 'Please use `limit` or `market` orders only.').strip() diff --git a/catalyst/exchange/exchange_portfolio.py b/catalyst/exchange/exchange_portfolio.py index b9f45fbb..1df8f9b2 100644 --- a/catalyst/exchange/exchange_portfolio.py +++ b/catalyst/exchange/exchange_portfolio.py @@ -40,7 +40,13 @@ class ExchangePortfolio(Portfolio): """ log.debug('creating order {}'.format(order.id)) - self.open_orders[order.id] = order + + open_orders = self.open_orders[order.asset] \ + if order.asset is self.open_orders else [] + + open_orders.append(order) + + self.open_orders[order.asset] = open_orders order_position = self.positions[order.asset] \ if order.asset in self.positions else None @@ -52,6 +58,17 @@ class ExchangePortfolio(Portfolio): order_position.amount += order.amount log.debug('open order added to portfolio') + def _remove_open_order(self, order): + try: + open_orders = self.open_orders[order.asset] + if order in open_orders: + open_orders.remove(order) + + except Exception: + raise ValueError( + 'unable to clear order not found in open order list.' + ) + def execute_order(self, order, transaction): """ Update the open orders and positions to apply an executed order. @@ -66,7 +83,7 @@ class ExchangePortfolio(Portfolio): """ log.debug('executing order {}'.format(order.id)) - del self.open_orders[order.id] + self._remove_open_order(order) order_position = self.positions[order.asset] \ if order.asset in self.positions else None @@ -99,7 +116,7 @@ class ExchangePortfolio(Portfolio): """ log.info('removing cancelled order {}'.format(order.id)) - del self.open_orders[order.id] + self._remove_open_order(order) order_position = self.positions[order.asset] \ if order.asset in self.positions else None diff --git a/catalyst/exchange/exchange_utils.py b/catalyst/exchange/exchange_utils.py index c2250187..0ae93ea8 100644 --- a/catalyst/exchange/exchange_utils.py +++ b/catalyst/exchange/exchange_utils.py @@ -627,3 +627,7 @@ def mixin_market_params(exchange_name, params, market): def from_ms_timestamp(ms): return pd.to_datetime(ms, unit='ms', utc=True) + + +def get_epoch(): + return pd.to_datetime('1970-1-1', utc=True) diff --git a/etc/requirements.txt b/etc/requirements.txt index dee52a34..be375309 100644 --- a/etc/requirements.txt +++ b/etc/requirements.txt @@ -81,5 +81,5 @@ empyrical==0.2.1 tables==3.3.0 #Catalyst dependencies -ccxt==1.10.251 +ccxt==1.10.283 From f47aa13dd361d3d90472987a9f2eb02a12a83de6 Mon Sep 17 00:00:00 2001 From: cyzanfar Date: Wed, 6 Dec 2017 17:58:42 -0500 Subject: [PATCH 17/52] Python version 3 compatible --- tests/exchange/test_server_bundle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exchange/test_server_bundle.py b/tests/exchange/test_server_bundle.py index 50dd98d7..42b2d3f3 100644 --- a/tests/exchange/test_server_bundle.py +++ b/tests/exchange/test_server_bundle.py @@ -51,7 +51,7 @@ class ValidateChunks(object): if data_frequency == 'daily': end = end - pd.Timedelta(hours=23, minutes=59) - print start, end, data_frequency + print(start, end, data_frequency) arrays = reader.load_raw_arrays(self.columns, start, end, [asset.sid, ]) From e42276affa7ba9ced99ce00d14f20965482fad86 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Wed, 6 Dec 2017 18:02:02 -0500 Subject: [PATCH 18/52] BLD: making some adjustment to the blotter to improve paper trading --- catalyst/examples/mean_reversion_simple.py | 2 +- catalyst/exchange/ccxt/ccxt_exchange.py | 18 +-- catalyst/exchange/exchange.py | 135 +++-------------- catalyst/exchange/exchange_algorithm.py | 159 +++++---------------- catalyst/exchange/exchange_blotter.py | 113 +++++++++++++-- catalyst/exchange/exchange_data_portal.py | 11 +- catalyst/exchange/exchange_utils.py | 11 ++ catalyst/exchange/factory.py | 4 +- catalyst/utils/run_algo.py | 13 -- 9 files changed, 179 insertions(+), 287 deletions(-) diff --git a/catalyst/examples/mean_reversion_simple.py b/catalyst/examples/mean_reversion_simple.py index 21e0c7cc..3d85e3f5 100644 --- a/catalyst/examples/mean_reversion_simple.py +++ b/catalyst/examples/mean_reversion_simple.py @@ -276,5 +276,5 @@ if __name__ == '__main__': algo_namespace=NAMESPACE, base_currency='eth', live_graph=False, - simulate_orders=True + simulate_orders=False ) diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py index 7f45627f..09879796 100644 --- a/catalyst/exchange/ccxt/ccxt_exchange.py +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -35,8 +35,7 @@ SUPPORTED_EXCHANGES = dict( class CCXT(Exchange): - def __init__(self, exchange_name, key, secret, base_currency, - portfolio=None): + def __init__(self, exchange_name, key, secret, base_currency): log.debug( 'finding {} in CCXT exchanges:\n{}'.format( exchange_name, ccxt.exchanges @@ -69,7 +68,6 @@ class CCXT(Exchange): self.load_assets() self.base_currency = base_currency - self._portfolio = portfolio self.transactions = defaultdict(list) self.num_candles_limit = 2000 @@ -508,18 +506,7 @@ class CCXT(Exchange): return orders - def _get_asset_from_order(self, order_id): - open_orders = self.portfolio.open_orders - order = next( - (open_orders[id] for id in open_orders if id == order_id), - None - ) # type: Order - return order.asset if order is not None else None - def get_order(self, order_id, asset_or_symbol=None): - if asset_or_symbol is None and self.portfolio is not None: - asset_or_symbol = self._get_asset_from_order(order_id) - if asset_or_symbol is None: log.debug( 'order not found in memory, the request might fail ' @@ -540,9 +527,6 @@ class CCXT(Exchange): order_id = order_param.id \ if isinstance(order_param, Order) else order_param - if asset_or_symbol is None and self.portfolio is not None: - asset_or_symbol = self._get_asset_from_order(order_id) - if asset_or_symbol is None: log.debug( 'order not found in memory, cancelling order might fail ' diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index ea938387..072f4701 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -20,7 +20,6 @@ from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \ NoDataAvailableOnExchange, NoValueForField from catalyst.exchange.exchange_execution import ExchangeStopLimitOrder, \ ExchangeLimitOrder, ExchangeStopOrder -from catalyst.exchange.exchange_portfolio import ExchangePortfolio from catalyst.exchange.exchange_utils import get_exchange_symbols, \ get_frequency, resample_history_df from catalyst.finance.order import ORDER_STATUS @@ -36,7 +35,6 @@ class Exchange: self.name = None self.assets = [] self._symbol_maps = [None, None] - self._portfolio = None self.minute_writer = None self.minute_reader = None self.base_currency = None @@ -46,27 +44,6 @@ class Exchange: self.request_cpt = None self.bundle = ExchangeBundle(self.name) - @property - def positions(self): - return self.portfolio.positions - - @property - def portfolio(self): - """ - The exchange portfolio - - Returns - ------- - ExchangePortfolio - """ - if self._portfolio is None: - self._portfolio = ExchangePortfolio( - start_date=pd.Timestamp.utcnow() - ) - self.synchronize_portfolio() - - return self._portfolio - @abstractproperty def account(self): pass @@ -313,53 +290,6 @@ class Exchange: """ pass - def check_open_orders(self): - """ - Loop through the list of open orders in the Portfolio object. - For each executed order found, create a transaction and apply to the - Portfolio. - - Returns - ------- - list[Transaction] - - """ - if self.portfolio.open_orders: - for order_id in list(self.portfolio.open_orders): - log.debug('found open order: {}'.format(order_id)) - - order, executed_price = self.get_order(order_id) - log.debug( - 'got updated order {} {}'.format( - order, executed_price - ) - ) - if order.status == ORDER_STATUS.FILLED: - transaction = Transaction( - asset=order.asset, - amount=order.amount, - dt=pd.Timestamp.utcnow(), - price=executed_price, - order_id=order.id, - commission=order.commission - ) - yield order, transaction - - # self.portfolio.execute_order(order, transaction) - - elif order.status == ORDER_STATUS.CANCELLED: - # self.portfolio.remove_order(order) - yield order, None - - else: - delta = pd.Timestamp.utcnow() - order.dt - log.info( - 'order {order_id} still open after {delta}'.format( - order_id=order_id, - delta=delta - ) - ) - def get_spot_value(self, assets, field, dt=None, data_frequency='minute'): """ Public API method that returns a scalar value representing the value @@ -668,7 +598,7 @@ class Exchange: return df - def synchronize_portfolio(self): + def calculate_totals(self, positions=None): """ Update the portfolio cash and position balances based on the latest ticker prices. @@ -677,42 +607,36 @@ class Exchange: log.debug('synchronizing portfolio with exchange {}'.format(self.name)) balances = self.get_balances() - base_position_available = balances[self.base_currency]['free'] \ + cash = balances[self.base_currency]['free'] \ if self.base_currency in balances else None - if base_position_available is None: + if cash is None: raise BaseCurrencyNotFoundError( base_currency=self.base_currency, - exchange=self.name.title() + exchange=self.name ) + log.debug('found base currency balance: {}'.format(cash)) - portfolio = self._portfolio - portfolio.cash = base_position_available - log.debug('found base currency balance: {}'.format(portfolio.cash)) - - if portfolio.starting_cash is None: - portfolio.starting_cash = portfolio.cash - - if portfolio.positions: - assets = list(portfolio.positions.keys()) + positions_value = 0.0 + if positions is not None: + assets = set([position.asset for position in positions]) tickers = self.tickers(assets) + log.debug('got tickers for positions: {}'.format(tickers)) - portfolio.positions_value = 0.0 for asset in tickers: - # TODO: convert if the position is not in the base currency ticker = tickers[asset] - position = portfolio.positions[asset] + positions = [p for p in positions if p.asset == asset] - position.last_sale_price = ticker['last_price'] - position.last_sale_date = ticker['last_traded'] + for position in positions: + position.last_sale_price = ticker['last_price'] + position.last_sale_date = ticker['last_traded'] - portfolio.positions_value += \ - position.amount * position.last_sale_price - portfolio.portfolio_value = \ - portfolio.positions_value + portfolio.cash + positions_value += \ + position.amount * position.last_sale_price - def order(self, asset, amount, limit_price=None, stop_price=None, - style=None): + return cash, positions_value + + def order(self, asset, amount, style): """Place an order. Parameters @@ -771,22 +695,8 @@ class Exchange: ) is_buy = (amount > 0) + display_price = style.get_limit_price(is_buy) - if limit_price is not None and stop_price is not None: - style = ExchangeStopLimitOrder( - limit_price, stop_price, exchange=self.name - ) - - elif limit_price is not None: - style = ExchangeLimitOrder(limit_price, exchange=self.name) - - elif stop_price is not None: - style = ExchangeStopOrder(stop_price, exchange=self.name) - - else: - style = MarketOrder(exchange=self.name) - - display_price = limit_price if limit_price is not None else stop_price log.debug( 'issuing {side} order of {amount} {symbol} for {type}: {price}'.format( side='buy' if is_buy else 'sell', @@ -797,12 +707,7 @@ class Exchange: ) ) - order = self.create_order(asset, amount, is_buy, style) - if order: - self._portfolio.create_order(order) - return order.id - else: - return None + return self.create_order(asset, amount, is_buy, style) # The methods below must be implemented for each exchange. @abstractmethod diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index 7f671c96..8cc84224 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -21,23 +21,19 @@ from time import sleep import logbook import pandas as pd -from catalyst.assets._assets import TradingPair import catalyst.protocol as zp from catalyst.algorithm import TradingAlgorithm from catalyst.constants import LOG_LEVEL -from catalyst.errors import OrderInBeforeTradingStart from catalyst.exchange.exchange_blotter import ExchangeBlotter from catalyst.exchange.exchange_errors import ( ExchangeRequestError, ExchangePortfolioDataError, - ExchangeTransactionError, - OrphanOrderError, OrderTypeNotSupported) -from catalyst.exchange.exchange_execution import ExchangeStopLimitOrder, \ - ExchangeLimitOrder, ExchangeStopOrder + OrderTypeNotSupported) +from catalyst.exchange.exchange_execution import ExchangeLimitOrder from catalyst.exchange.exchange_utils import save_algo_object, get_algo_object, \ get_algo_folder, get_algo_df, \ - save_algo_df + save_algo_df, group_assets_by_exchange from catalyst.exchange.live_graph_clock import LiveGraphClock from catalyst.exchange.simple_clock import SimpleClock from catalyst.exchange.stats_utils import get_pretty_stats @@ -45,10 +41,8 @@ from catalyst.finance.execution import MarketOrder from catalyst.finance.performance.period import calc_period_stats from catalyst.gens.tradesimulation import AlgorithmSimulator from catalyst.utils.api_support import ( - api_method, - disallowed_in_before_trading_start) -from catalyst.utils.input_validation import error_keywords, ensure_upper_case, \ - expect_types + api_method) +from catalyst.utils.input_validation import error_keywords, ensure_upper_case from catalyst.utils.math_utils import round_nearest from catalyst.utils.preprocess import preprocess @@ -75,7 +69,8 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm): data_frequency=self.data_frequency, # Default to NeverCancel in catalyst cancel_policy=self.cancel_policy, - simulate_orders=self.simulate_orders + simulate_orders=self.simulate_orders, + exchanges=self.exchanges ) @staticmethod @@ -441,22 +436,25 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): def updated_account(self): return self.perf_tracker.get_account(False) - def _synchronize_portfolio(self, attempt_index=0): + def update_positions(self, attempt_index=0): + tracker = self.perf_tracker.position_tracker + try: - for exchange_name in self.exchanges: - exchange = self.exchanges[exchange_name] + # Position keys correspond to assets + assets = list(tracker.positions) + exchange_assets = group_assets_by_exchange(assets) + for exchange_name in exchange_assets: + assets = exchange_assets[exchange_name] + exchange_positions = \ + [tracker.positions[asset] for asset in assets] - exchange.synchronize_portfolio() + exchange = self.exchanges[exchange_name] # Type: Exchange + cash, positions_value = \ + exchange.calculate_totals(exchange_positions) - # Applying the updated last_sales_price to the positions - # in the performance tracker. This seems a bit redundant - # but it will make sense when we have multiple exchange portfolios - # feeding into the same performance tracker. - tracker = self.perf_tracker.todays_performance.position_tracker - for asset in exchange.portfolio.positions: - position = exchange.portfolio.positions[asset] + for position in exchange_positions: tracker.update_position( - asset=asset, + asset=position.asset, last_sale_date=position.last_sale_date, last_sale_price=position.last_sale_price ) @@ -467,7 +465,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): ) if attempt_index < self.retry_synchronize_portfolio: sleep(self.retry_delay) - self._synchronize_portfolio(attempt_index + 1) + self.update_positions(attempt_index + 1) else: raise ExchangePortfolioDataError( data_type='update-portfolio', @@ -576,7 +574,10 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): order = self.blotter.orders[transaction.order_id] self.perf_tracker.process_order(order) - self.perf_tracker.update_performance() + if len(new_transactions) > 0: + self.perf_tracker.update_performance() + + self.update_positions() if self._handle_data: self._handle_data(self, data) @@ -643,103 +644,21 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): log.warn('unable to save minute perfs to disk: {}'.format(e)) try: - for exchange_name in self.exchanges: - exchange = self.exchanges[exchange_name] - save_algo_object( - algo_name=self.algo_namespace, - key='portfolio_{}'.format(exchange_name), - obj=exchange.portfolio - ) + blotter_params = dict( + open_orders=self.blotter.open_orders, + orders=self.blotter.orders, + new_orders=self.blotter.new_orders, + data_frequency=self.blotter.data_frequency, + current_dt=self.blotter.current_dt, + ) + save_algo_object( + algo_name=self.algo_namespace, + key='blotter', + obj=blotter_params, + ) except Exception as e: log.warn('unable to save portfolio to disk: {}'.format(e)) - def _order(self, - asset, - amount, - limit_price=None, - stop_price=None, - style=None, - attempt_index=0): - try: - exchange = self.exchanges[asset.exchange] - return exchange.order(asset, amount, limit_price, - stop_price, - style) - except ExchangeRequestError as e: - log.warn( - 'order attempt {}: {}'.format(attempt_index, e) - ) - if attempt_index < self.retry_order: - sleep(self.retry_delay) - return self._order( - asset, amount, limit_price, stop_price, style, - attempt_index + 1) - else: - raise ExchangeTransactionError( - transaction_type='order', - attempts=attempt_index, - error=e - ) - - @api_method - @disallowed_in_before_trading_start(OrderInBeforeTradingStart()) - @expect_types(asset=TradingPair) - def order(self, - asset, - amount, - limit_price=None, - stop_price=None, - style=None): - """ - We use the exchange specific portfolio to place orders. - The cumulative portfolio does not contain open orders but exchange - portfolios do. - - Parameters - ---------- - asset: TradingPair - amount: float - limit_price: float - stop_price: float - style: Style - order: Order - The catalyst order object or None - """ - - if self.simulate_orders: - order_id = super(ExchangeTradingAlgorithmLive, self).order( - asset, amount, limit_price, stop_price, style - ) - log.debug('created a simulated order {}'.format(order_id)) - - else: - amount, style = self._calculate_order( - asset, amount, limit_price, stop_price, style - ) - order_id = self._order( - asset, amount, limit_price, stop_price, style - ) - - if order_id is not None: - current_order = None - for order in self.blotter.open_orders[asset]: - if current_order is None and order.id == order_id: - self.perf_tracker.process_order(order) - current_order = order - - if current_order is not None: - return current_order - - else: - raise OrphanOrderError( - order_id=order_id, - exchange=asset.exchange - ) - else: - log.warn('unable to order {} {} on exchange {}'.format( - amount, asset.symbol, asset.exchange)) - return None - @api_method def batch_market_order(self, share_counts): raise NotImplementedError() diff --git a/catalyst/exchange/exchange_blotter.py b/catalyst/exchange/exchange_blotter.py index 72e6d539..79757221 100644 --- a/catalyst/exchange/exchange_blotter.py +++ b/catalyst/exchange/exchange_blotter.py @@ -1,15 +1,18 @@ from time import sleep +import pandas as pd from catalyst.assets._assets import TradingPair from logbook import Logger from catalyst.constants import LOG_LEVEL from catalyst.exchange.exchange_errors import ExchangeRequestError, \ - ExchangePortfolioDataError + ExchangePortfolioDataError, OrphanOrderError, ExchangeTransactionError from catalyst.finance.blotter import Blotter from catalyst.finance.commission import CommissionModel +from catalyst.finance.order import ORDER_STATUS from catalyst.finance.slippage import SlippageModel -from catalyst.finance.transaction import create_transaction +from catalyst.finance.transaction import create_transaction, Transaction +from catalyst.utils.input_validation import expect_types log = Logger('exchange_blotter', level=LOG_LEVEL) @@ -127,6 +130,11 @@ class ExchangeBlotter(Blotter): def __init__(self, *args, **kwargs): self.simulate_orders = kwargs.pop('simulate_orders', False) + self.exchanges = kwargs.pop('exchanges', None) + if not self.exchanges: + raise ValueError('ExchangeBlotter must have an `exchanges` ' + 'attribute.') + super(ExchangeBlotter, self).__init__(*args, **kwargs) # Using the equity models for now @@ -139,22 +147,106 @@ class ExchangeBlotter(Blotter): TradingPair: TradingPairFeeSchedule() } + def exchange_order(self, asset, amount, style=None, attempt_index=0): + try: + exchange = self.exchanges[asset.exchange] + return exchange.order( + asset, amount, style + ) + except ExchangeRequestError as e: + log.warn( + 'order attempt {}: {}'.format(attempt_index, e) + ) + if attempt_index < self.retry_order: + sleep(self.retry_delay) + + return self.exchange_order( + asset, amount, style, attempt_index + 1 + ) + else: + raise ExchangeTransactionError( + transaction_type='order', + attempts=attempt_index, + error=e + ) + + @expect_types(asset=TradingPair) + def order(self, asset, amount, style, order_id=None): + if self.simulate_orders: + return super(ExchangeBlotter, self).order( + asset, amount, style, order_id + ) + + else: + order = self.exchange_order( + asset, amount, style + ) + + self.open_orders[order.asset].append(order) + self.orders[order.id] = order + self.new_orders.append(order) + + return order.id + + def check_open_orders(self): + """ + Loop through the list of open orders in the Portfolio object. + For each executed order found, create a transaction and apply to the + Portfolio. + + Returns + ------- + list[Transaction] + + """ + for asset in self.open_orders: + exchange = self.exchanges[asset.exchange] + + for order in self.open_orders[asset]: + log.debug('found open order: {}'.format(order.id)) + + order, executed_price = exchange.get_order(order.id, asset) + log.debug( + 'got updated order {} {}'.format( + order, executed_price + ) + ) + if order.status == ORDER_STATUS.FILLED: + transaction = Transaction( + asset=order.asset, + amount=order.amount, + dt=pd.Timestamp.utcnow(), + price=executed_price, + order_id=order.id, + commission=order.commission + ) + yield order, transaction + + elif order.status == ORDER_STATUS.CANCELLED: + yield order, None + + else: + delta = pd.Timestamp.utcnow() - order.dt + log.info( + 'order {order_id} still open after {delta}'.format( + order_id=order.id, + delta=delta + ) + ) + def get_exchange_transactions(self, attempt_index=0): closed_orders = [] transactions = [] commissions = [] try: - for exchange_name in self.exchanges: - exchange = self.exchanges[exchange_name] - for order, txn in exchange.check_open_orders(): + for order, txn in self.check_open_orders(): + order.dt = txn.dt - order.dt = txn.dt + transactions.append(txn) - transactions.append(txn) - - if not order.open: - closed_orders.append(order) + if not order.open: + closed_orders.append(order) return transactions, commissions, closed_orders @@ -165,6 +257,7 @@ class ExchangeBlotter(Blotter): if attempt_index < self.retry_check_open_orders: sleep(self.retry_delay) return self.get_exchange_transactions(attempt_index + 1) + else: raise ExchangePortfolioDataError( data_type='order-status', diff --git a/catalyst/exchange/exchange_data_portal.py b/catalyst/exchange/exchange_data_portal.py index 4cfac2b7..f5a10a37 100644 --- a/catalyst/exchange/exchange_data_portal.py +++ b/catalyst/exchange/exchange_data_portal.py @@ -13,7 +13,8 @@ from catalyst.exchange.exchange_errors import ( ExchangeRequestError, ExchangeBarDataError, PricingDataNotLoadedError) -from catalyst.exchange.exchange_utils import get_frequency, resample_history_df +from catalyst.exchange.exchange_utils import get_frequency, \ + resample_history_df, group_assets_by_exchange log = Logger('DataPortalExchange', level=LOG_LEVEL) @@ -38,13 +39,7 @@ class DataPortalExchangeBase(DataPortal): ffill=True, attempt_index=0): try: - exchange_assets = dict() - for asset in assets: - if asset.exchange not in exchange_assets: - exchange_assets[asset.exchange] = list() - - exchange_assets[asset.exchange].append(asset) - + exchange_assets = group_assets_by_exchange(assets) if len(exchange_assets) > 1: df_list = [] for exchange_name in exchange_assets: diff --git a/catalyst/exchange/exchange_utils.py b/catalyst/exchange/exchange_utils.py index 0ae93ea8..e4f4cd7f 100644 --- a/catalyst/exchange/exchange_utils.py +++ b/catalyst/exchange/exchange_utils.py @@ -631,3 +631,14 @@ def from_ms_timestamp(ms): def get_epoch(): return pd.to_datetime('1970-1-1', utc=True) + + +def group_assets_by_exchange(assets): + exchange_assets = dict() + for asset in assets: + if asset.exchange not in exchange_assets: + exchange_assets[asset.exchange] = list() + + exchange_assets[asset.exchange].append(asset) + + return exchange_assets diff --git a/catalyst/exchange/factory.py b/catalyst/exchange/factory.py index 8099b208..b0002f32 100644 --- a/catalyst/exchange/factory.py +++ b/catalyst/exchange/factory.py @@ -6,8 +6,7 @@ from catalyst.exchange.exchange_utils import get_exchange_auth, \ get_exchange_folder -def get_exchange(exchange_name, base_currency=None, portfolio=None, - must_authenticate=False): +def get_exchange(exchange_name, base_currency=None, must_authenticate=False): exchange_auth = get_exchange_auth(exchange_name) has_auth = (exchange_auth['key'] != '' and exchange_auth['secret'] != '') @@ -24,7 +23,6 @@ def get_exchange(exchange_name, base_currency=None, portfolio=None, key=exchange_auth['key'], secret=exchange_auth['secret'], base_currency=base_currency, - portfolio=portfolio ) diff --git a/catalyst/utils/run_algo.py b/catalyst/utils/run_algo.py index a20490bc..f7219330 100644 --- a/catalyst/utils/run_algo.py +++ b/catalyst/utils/run_algo.py @@ -152,22 +152,9 @@ def _run(handle_data, exchanges = dict() for exchange_name in exchange_list: - # Looking for the portfolio from the cache first - portfolio = get_algo_object( - algo_name=algo_namespace, - key='portfolio_{}'.format(exchange_name), - environ=environ - ) - - if portfolio is None: - portfolio = ExchangePortfolio( - start if start is not None else pd.Timestamp.utcnow() - ) - exchanges[exchange_name] = get_exchange( exchange_name=exchange_name, base_currency=base_currency, - portfolio=portfolio, must_authenticate=live, ) From 9688d71e23cb82e23090ae6950472a2f724a81c2 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Wed, 6 Dec 2017 23:32:26 -0500 Subject: [PATCH 19/52] BLD: tested blotter changes with live trading --- catalyst/assets/_assets.pyx | 29 ++++- catalyst/examples/mean_reversion_simple.py | 35 +++--- catalyst/exchange/ccxt/ccxt_exchange.py | 49 +++++--- catalyst/exchange/exchange.py | 7 +- catalyst/exchange/exchange_algorithm.py | 71 ++++++------ catalyst/exchange/exchange_utils.py | 4 + catalyst/exchange/stats_utils.py | 123 +++++++++++++++------ catalyst/utils/run_algo.py | 11 +- 8 files changed, 219 insertions(+), 110 deletions(-) diff --git a/catalyst/assets/_assets.pyx b/catalyst/assets/_assets.pyx index af1e81a6..07e7e813 100644 --- a/catalyst/assets/_assets.pyx +++ b/catalyst/assets/_assets.pyx @@ -405,6 +405,9 @@ cdef class TradingPair(Asset): cdef readonly float taker cdef readonly int trading_state cdef readonly object data_source + cdef readonly float max_trade_size + cdef readonly float lot + cdef readonly int decimals _kwargnames = frozenset({ 'sid', @@ -423,10 +426,13 @@ cdef class TradingPair(Asset): 'end_minute', 'exchange_symbol', 'min_trade_size', + 'max_trade_size', + 'lot', 'maker', 'taker', 'trading_state', - 'data_source' + 'data_source', + 'decimals' }) def __init__(self, object symbol, @@ -443,8 +449,11 @@ cdef class TradingPair(Asset): object auto_close_date=None, object exchange_full=None, float min_trade_size=0.0001, + float max_trade_size=1000000, float maker=0.0015, float taker=0.0025, + float lot=0, + int decimals = 8, int trading_state=0, object data_source='catalyst'): """ @@ -509,9 +518,12 @@ cdef class TradingPair(Asset): :param auto_close_date: :param exchange_full: :param min_trade_size: + :param max_trade_size: :param maker: :param taker: :param data_source + :param decimals + :param lot """ symbol = symbol.lower() @@ -535,6 +547,9 @@ cdef class TradingPair(Asset): if end_date is None: end_date = pd.Timestamp.utcnow() + timedelta(days=365) + if lot == 0 and min_trade_size > 0: + lot = min_trade_size + super().__init__( sid, exchange, @@ -556,6 +571,9 @@ cdef class TradingPair(Asset): self.exchange_symbol = exchange_symbol self.trading_state = trading_state self.data_source = data_source + self.max_trade_size = max_trade_size + self.lot = lot + self.decimals = decimals def __repr__(self): return 'Trading Pair {symbol}({sid}) Exchange: {exchange}, ' \ @@ -582,6 +600,7 @@ cdef class TradingPair(Asset): """ Convert to a python dict. """ + #TODO: missing fields super_dict = super(TradingPair, self).to_dict() super_dict['end_daily'] = self.end_daily super_dict['end_minute'] = self.end_minute @@ -610,6 +629,7 @@ cdef class TradingPair(Asset): and whose second element is a tuple of all the attributes that should be serialized/deserialized during pickling. """ + #TODO: make sure that all fields set there return (self.__class__, (self.symbol, self.exchange, self.start_date, @@ -620,7 +640,12 @@ cdef class TradingPair(Asset): self.first_traded, self.auto_close_date, self.exchange_full, - self.min_trade_size)) + self.min_trade_size, + self.max_trade_size, + self.lot, + self.decimals, + self.taker, + self.maker)) def make_asset_array(int size, Asset asset): cdef np.ndarray out = np.empty([size], dtype=object) diff --git a/catalyst/examples/mean_reversion_simple.py b/catalyst/examples/mean_reversion_simple.py index 3d85e3f5..db0a90c3 100644 --- a/catalyst/examples/mean_reversion_simple.py +++ b/catalyst/examples/mean_reversion_simple.py @@ -12,7 +12,8 @@ from logbook import Logger from catalyst import run_algorithm from catalyst.api import symbol, record, order_target_percent, get_open_orders -from catalyst.exchange.stats_utils import extract_transactions +from catalyst.exchange.stats_utils import extract_transactions, \ + get_pretty_stats # We give a name to the algorithm which Catalyst will use to persist its state. # In this example, Catalyst will create the `.catalyst/data/live_algos` # directory. If we stop and start the algorithm, Catalyst will resume its @@ -33,11 +34,11 @@ def initialize(context): # parameters or values you're going to use. # In our example, we're looking at Neo in Ether. - context.neo_eth = symbol('neo_eth') + context.market = symbol('rdn_eth') context.base_price = None context.current_day = None - context.RSI_OVERSOLD = 55 + context.RSI_OVERSOLD = 65 context.RSI_OVERBOUGHT = 82 context.CANDLE_SIZE = '5T' @@ -59,14 +60,14 @@ def handle_data(context, data): context.current_day = today # We're computing the volume-weighted-average-price of the security - # defined above, in the context.neo_eth variable. For this example, we're + # defined above, in the context.market variable. For this example, we're # using three bars on the 15 min bars. # The frequency attribute determine the bar size. We use this convention # for the frequency alias: # http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases prices = data.history( - context.neo_eth, + context.market, fields='close', bar_count=50, frequency=context.CANDLE_SIZE @@ -81,7 +82,7 @@ def handle_data(context, data): # We need a variable for the current price of the security to compare to # the average. Since we are requesting two fields, data.current() # returns a DataFrame with - current = data.current(context.neo_eth, fields=['close', 'volume']) + current = data.current(context.market, fields=['close', 'volume']) price = current['close'] # If base_price is not set, we use the current value. This is the @@ -110,19 +111,20 @@ def handle_data(context, data): # Since we are using limit orders, some orders may not execute immediately # we wait until all orders are executed before considering more trades. - orders = get_open_orders(context.neo_eth) + orders = get_open_orders(context.market) if len(orders) > 0: + log.info('exiting because orders are open: {}'.format(orders)) return # Exit if we cannot trade - if not data.can_trade(context.neo_eth): + if not data.can_trade(context.market): return # Another powerful built-in feature of the Catalyst backtester is the # portfolio object. The portfolio object tracks your positions, cash, # cost basis of specific holdings, and more. In this line, we calculate # how long or short our position is at this minute. - pos_amount = context.portfolio.positions[context.neo_eth].amount + pos_amount = context.portfolio.positions[context.market].amount if rsi[-1] <= context.RSI_OVERSOLD and pos_amount == 0: log.info( @@ -133,7 +135,7 @@ def handle_data(context, data): # Set a style for limit orders, limit_price = price * 1.005 order_target_percent( - context.neo_eth, 1, limit_price=limit_price + context.market, 1, limit_price=limit_price ) context.traded_today = True @@ -145,7 +147,7 @@ def handle_data(context, data): ) limit_price = price * 0.995 order_target_percent( - context.neo_eth, 0, limit_price=limit_price + context.market, 0, limit_price=limit_price ) context.traded_today = True @@ -168,7 +170,7 @@ def analyze(context=None, perf=None): perf.loc[:, 'price'].plot(ax=ax2, label='Price') ax2.set_ylabel('{asset}\n({base})'.format( - asset=context.neo_eth.symbol, base=base_currency + asset=context.market.symbol, base=base_currency )) transaction_df = extract_transactions(perf) @@ -229,7 +231,7 @@ def analyze(context=None, perf=None): ) plt.legend(loc=3) start, end = ax6.get_ylim() - ax6.yaxis.set_ticks(np.arange(0, end, end/5)) + ax6.yaxis.set_ticks(np.arange(0, end, end / 5)) # Show the plot. plt.gcf().set_size_inches(18, 8) @@ -267,14 +269,15 @@ if __name__ == '__main__': elif MODE == 'live': run_algorithm( - capital_base=0.1, + capital_base=0.05, initialize=initialize, handle_data=handle_data, analyze=analyze, - exchange_name='bittrex', + exchange_name='binance', live=True, algo_namespace=NAMESPACE, base_currency='eth', live_graph=False, - simulate_orders=False + simulate_orders=False, + stats_output='s3://something' ) diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py index 09879796..6724a8c7 100644 --- a/catalyst/exchange/ccxt/ccxt_exchange.py +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -4,23 +4,22 @@ from collections import defaultdict import ccxt import pandas as pd import six -from ccxt import ExchangeNotAvailable +from catalyst.assets._assets import TradingPair +from ccxt import ExchangeNotAvailable, InvalidOrder +from logbook import Logger from six import string_types -from catalyst.finance.order import Order, ORDER_STATUS - from catalyst.algorithm import MarketOrder -from catalyst.assets._assets import TradingPair -from logbook import Logger - from catalyst.constants import LOG_LEVEL -from catalyst.exchange.exchange import Exchange, ExchangeLimitOrder +from catalyst.exchange.exchange import Exchange from catalyst.exchange.exchange_bundle import ExchangeBundle from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \ ExchangeSymbolsNotFound, ExchangeRequestError, InvalidOrderStyle, \ - ExchangeNotFoundError + ExchangeNotFoundError, CreateOrderError +from catalyst.exchange.exchange_execution import ExchangeLimitOrder from catalyst.exchange.exchange_utils import mixin_market_params, \ from_ms_timestamp, get_epoch +from catalyst.finance.order import Order, ORDER_STATUS log = Logger('CCXT', level=LOG_LEVEL) @@ -288,7 +287,7 @@ class CCXT(Exchange): else: return None - def create_trading_pair(self, market, asset_def, is_local): + def create_trading_pair(self, market, asset_def=None, is_local=False): """ Creating a TradingPair from market and asset data. @@ -346,6 +345,7 @@ class CCXT(Exchange): for market in self.markets: asset_defs = self.get_asset_defs(market) + asset = None for asset_def in asset_defs: if asset_def[0] is not None or not asset_defs[1]: try: @@ -356,8 +356,12 @@ class CCXT(Exchange): ) self.assets.append(asset) - except TypeError: - pass + except TypeError as e: + log.warn('unable to add asset: {}'.format(e)) + + if asset is None: + asset = self.create_trading_pair(market=market) + self.assets.append(asset) def get_balances(self): try: @@ -460,26 +464,45 @@ class CCXT(Exchange): side = 'buy' if amount > 0 else 'sell' + if hasattr(self.api, 'amount_to_lots'): + adj_amount = self.api.amount_to_lots( + symbol=symbol, + amount=abs(amount), + ) + if adj_amount != abs(amount): + log.info( + 'adjusted order amount {} to {} based on lot size'.format( + abs(amount), adj_amount, + ) + ) + else: + adj_amount = abs(amount) + try: result = self.api.create_order( symbol=symbol, type=order_type, side=side, - amount=abs(amount), + amount=adj_amount, price=price ) except ExchangeNotAvailable as e: log.debug('unable to create order: {}'.format(e)) raise ExchangeRequestError(error=e) + except InvalidOrder as e: + log.warn('the exchange rejected the order: {}'.format(e)) + raise CreateOrderError(exchange=self.name, error=e) + if 'info' not in result: raise ValueError('cannot use order without info attribute') + final_amount = adj_amount if side == 'buy' else -adj_amount order_id = result['id'] order = Order( dt=pd.Timestamp.utcnow(), asset=asset, - amount=amount, + amount=final_amount, stop=style.get_stop_price(is_buy), limit=style.get_limit_price(is_buy), id=order_id diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index 072f4701..642d3612 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -8,7 +8,6 @@ import pandas as pd from catalyst.assets._assets import TradingPair from logbook import Logger -from catalyst.algorithm import MarketOrder from catalyst.constants import LOG_LEVEL from catalyst.data.data_portal import BASE_FIELDS from catalyst.exchange.bundle_utils import get_start_dt, \ @@ -18,12 +17,8 @@ from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \ BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \ PricingDataNotLoadedError, \ NoDataAvailableOnExchange, NoValueForField -from catalyst.exchange.exchange_execution import ExchangeStopLimitOrder, \ - ExchangeLimitOrder, ExchangeStopOrder from catalyst.exchange.exchange_utils import get_exchange_symbols, \ get_frequency, resample_history_df -from catalyst.finance.order import ORDER_STATUS -from catalyst.finance.transaction import Transaction log = Logger('Exchange', level=LOG_LEVEL) @@ -618,7 +613,7 @@ class Exchange: log.debug('found base currency balance: {}'.format(cash)) positions_value = 0.0 - if positions is not None: + if positions: assets = set([position.asset for position in positions]) tickers = self.tickers(assets) log.debug('got tickers for positions: {}'.format(tickers)) diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index 8cc84224..013fc618 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -36,7 +36,7 @@ from catalyst.exchange.exchange_utils import save_algo_object, get_algo_object, save_algo_df, group_assets_by_exchange from catalyst.exchange.live_graph_clock import LiveGraphClock from catalyst.exchange.simple_clock import SimpleClock -from catalyst.exchange.stats_utils import get_pretty_stats +from catalyst.exchange.stats_utils import get_pretty_stats, stats_to_s3 from catalyst.finance.execution import MarketOrder from catalyst.finance.performance.period import calc_period_stats from catalyst.gens.tradesimulation import AlgorithmSimulator @@ -305,6 +305,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): def __init__(self, *args, **kwargs): self.algo_namespace = kwargs.pop('algo_namespace', None) self.live_graph = kwargs.pop('live_graph', None) + self.stats_output = kwargs.pop('stats_output', None) self._clock = None self.frame_stats = deque(maxlen=60) @@ -436,15 +437,19 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): def updated_account(self): return self.perf_tracker.get_account(False) - def update_positions(self, attempt_index=0): + def synchronize_portfolio(self, attempt_index=0): tracker = self.perf_tracker.position_tracker + total_cash = 0.0 + total_positions_value = 0.0 try: # Position keys correspond to assets assets = list(tracker.positions) exchange_assets = group_assets_by_exchange(assets) - for exchange_name in exchange_assets: - assets = exchange_assets[exchange_name] + for exchange_name in self.exchanges: + assets = exchange_assets[exchange_name] \ + if exchange_name in exchange_assets else [] + exchange_positions = \ [tracker.positions[asset] for asset in assets] @@ -452,6 +457,9 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): cash, positions_value = \ exchange.calculate_totals(exchange_positions) + total_cash += cash + total_positions_value += total_positions_value + for position in exchange_positions: tracker.update_position( asset=position.asset, @@ -459,13 +467,17 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): last_sale_price=position.last_sale_price ) + if total_cash < self.portfolio.cash: + raise ValueError('Cash on exchanges is lower than the algo.') + + return total_cash, total_positions_value except ExchangeRequestError as e: log.warn( 'update portfolio attempt {}: {}'.format(attempt_index, e) ) if attempt_index < self.retry_synchronize_portfolio: sleep(self.retry_delay) - self.update_positions(attempt_index + 1) + self.synchronize_portfolio(attempt_index + 1) else: raise ExchangePortfolioDataError( data_type='update-portfolio', @@ -565,20 +577,15 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): new_transactions, new_commissions, closed_orders = \ self.blotter.get_transactions(data) - self.blotter.prune_orders(closed_orders) - - for transaction in new_transactions: - self.perf_tracker.process_transaction(transaction) - - # since this order was modified, record it - order = self.blotter.orders[transaction.order_id] - self.perf_tracker.process_order(order) - if len(new_transactions) > 0: self.perf_tracker.update_performance() - self.update_positions() - + cash, positions_value = self.synchronize_portfolio() + log.info( + 'got totals from exchanges, cash: {} positions: {}'.format( + cash, positions_value + ) + ) if self._handle_data: self._handle_data(self, data) @@ -612,12 +619,26 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): 'statistics for the last {stats_minutes} minutes:\n{stats}'.format( stats_minutes=self.stats_minutes, stats=get_pretty_stats( - stats_df=print_df, + df=print_df, recorded_cols=recorded_cols, num_rows=self.stats_minutes ) )) + if self.stats_output is not None: + if 's3://' in self.stats_output: + stats_to_s3( + uri=self.stats_output, + df=print_df, + algo_namespace=self.algo_namespace, + recorded_cols=recorded_cols, + ) + + else: + raise ValueError( + 'Only S3 stats output is supported for now.' + ) + today = pd.to_datetime('today', utc=True) daily_stats = self.prepare_period_stats( start_dt=today, @@ -643,22 +664,6 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): except Exception as e: log.warn('unable to save minute perfs to disk: {}'.format(e)) - try: - blotter_params = dict( - open_orders=self.blotter.open_orders, - orders=self.blotter.orders, - new_orders=self.blotter.new_orders, - data_frequency=self.blotter.data_frequency, - current_dt=self.blotter.current_dt, - ) - save_algo_object( - algo_name=self.algo_namespace, - key='blotter', - obj=blotter_params, - ) - except Exception as e: - log.warn('unable to save portfolio to disk: {}'.format(e)) - @api_method def batch_market_order(self, share_counts): raise NotImplementedError() diff --git a/catalyst/exchange/exchange_utils.py b/catalyst/exchange/exchange_utils.py index e4f4cd7f..af7f2fee 100644 --- a/catalyst/exchange/exchange_utils.py +++ b/catalyst/exchange/exchange_utils.py @@ -604,6 +604,7 @@ def mixin_market_params(exchange_name, params, market): # TODO: make this more externalized / configurable if 'lot' in market: params['min_trade_size'] = market['lot'] + params['lot'] = market['lot'] if exchange_name == 'bitfinex': params['maker'] = 0.001 @@ -624,6 +625,9 @@ def mixin_market_params(exchange_name, params, market): if 'minimum_order_size' in info: params['min_trade_size'] = float(info['minimum_order_size']) + if 'lot' not in params: + params['lot'] = params['min_trade_size'] + def from_ms_timestamp(ms): return pd.to_datetime(ms, unit='ms', utc=True) diff --git a/catalyst/exchange/stats_utils.py b/catalyst/exchange/stats_utils.py index 845fccc8..77f4d283 100644 --- a/catalyst/exchange/stats_utils.py +++ b/catalyst/exchange/stats_utils.py @@ -2,6 +2,7 @@ import numbers import numpy as np import pandas as pd +import time def trend_direction(series): @@ -119,29 +120,20 @@ def vwap(df): return ret -def get_pretty_stats(stats_df, recorded_cols=None, num_rows=10): - """ - Format and print the last few rows of a statistics DataFrame. - See the pyfolio project for the data structure. +def format_positions(positions): + parts = [] + for position in positions: + msg = '{amount:.2f}{base} cost basis {cost_basis:.8f}{quote}'.format( + amount=position['amount'], + base=position['sid'].base_currency, + cost_basis=position['cost_basis'], + quote=position['sid'].quote_currency + ) + parts.append(msg) + return ', '.join(parts) - Parameters - ---------- - stats_df: DataFrame - num_rows: int - - Returns - ------- - str - - """ - stats_df.set_index('period_close', drop=True, inplace=True) - stats_df.dropna(axis=1, how='all', inplace=True) - - pd.set_option('display.expand_frame_repr', False) - pd.set_option('precision', 3) - pd.set_option('display.width', 1000) - pd.set_option('display.max_colwidth', 1000) +def prepare_stats(df, recorded_cols=None): columns = ['starting_cash', 'ending_cash', 'portfolio_value', 'pnl', 'long_exposure', 'short_exposure', 'orders', 'transactions', 'positions'] @@ -150,31 +142,90 @@ def get_pretty_stats(stats_df, recorded_cols=None, num_rows=10): for column in recorded_cols: columns.append(column) - def format_positions(positions): - parts = [] - for position in positions: - msg = '{amount:.2f}{base} cost basis {cost_basis:.4f}{quote}'.format( - amount=position['amount'], - base=position['sid'].base_currency, - cost_basis=position['cost_basis'], - quote=position['sid'].quote_currency - ) - parts.append(msg) - return ', '.join(parts) + df = df.copy(True) + + df.set_index('period_close', drop=True, inplace=True) + df.dropna(axis=1, how='all', inplace=True) + + df['orders'] = df['orders'].apply(lambda orders: len(orders)) + df['transactions'] = df['transactions'].apply( + lambda transactions: len(transactions) + ) + df['positions'] = df['positions'].apply(format_positions) + + return df, columns + + +def get_pretty_stats(df, recorded_cols=None, num_rows=10): + """ + Format and print the last few rows of a statistics DataFrame. + See the pyfolio project for the data structure. + + Parameters + ---------- + df: pd.DataFrame + num_rows: int + + Returns + ------- + str + + """ + df, columns = prepare_stats(df, recorded_cols=recorded_cols) + + pd.set_option('display.expand_frame_repr', False) + pd.set_option('precision', 3) + pd.set_option('display.width', 1000) + pd.set_option('display.max_colwidth', 1000) formatters = { - 'orders': lambda orders: len(orders), - 'transactions': lambda transactions: len(transactions), 'returns': lambda returns: "{0:.4f}".format(returns), - 'positions': format_positions } - return stats_df.tail(num_rows).to_string( + return df.tail(num_rows).to_string( columns=columns, formatters=formatters ) +def get_csv_stats(df, recorded_cols=None): + """ + Create a CSV buffer from the stats DataFrame. + + Parameters + ---------- + path: str + df: pd.DataFrame + recorded_cols: list[str] + + Returns + ------- + + """ + df, columns = prepare_stats(df, recorded_cols=recorded_cols) + + return df.to_csv( + None, + columns=columns, + encoding='utf-8', + ).encode() + + +def stats_to_s3(uri, df, algo_namespace, recorded_cols=None): + import boto3 + s3 = boto3.resource('s3') + + bytes_to_write = get_csv_stats(df, recorded_cols=recorded_cols) + + timestr = time.strftime('%Y%m%d-%H%M%S') + + parts = uri.split('//') + obj = s3.Object(parts[1], 'stats/{}-{}.csv'.format( + timestr, algo_namespace + )) + obj.put(Body=bytes_to_write) + + def df_to_string(df): """ Create a formatted str representation of the DataFrame. diff --git a/catalyst/utils/run_algo.py b/catalyst/utils/run_algo.py index f7219330..92a5e335 100644 --- a/catalyst/utils/run_algo.py +++ b/catalyst/utils/run_algo.py @@ -91,7 +91,8 @@ def _run(handle_data, algo_namespace, base_currency, live_graph, - simulate_orders): + simulate_orders, + stats_output): """Run a backtest for the given algorithm. This is shared between the cli and :func:`catalyst.run_algo`. @@ -151,7 +152,6 @@ def _run(handle_data, exchanges = dict() for exchange_name in exchange_list: - exchanges[exchange_name] = get_exchange( exchange_name=exchange_name, base_currency=base_currency, @@ -266,7 +266,8 @@ def _run(handle_data, exchanges=exchanges, algo_namespace=algo_namespace, live_graph=live_graph, - simulate_orders=simulate_orders + simulate_orders=simulate_orders, + stats_output=stats_output, ) elif exchanges: # Removed the existing Poloniex fork to keep things simple @@ -429,6 +430,7 @@ def run_algorithm(initialize, algo_namespace=None, live_graph=False, simulate_orders=True, + stats_output=None, output=os.devnull): """Run a trading algorithm. @@ -551,5 +553,6 @@ def run_algorithm(initialize, algo_namespace=algo_namespace, base_currency=base_currency, live_graph=live_graph, - simulate_orders=simulate_orders + simulate_orders=simulate_orders, + stats_output=stats_output ) From 5b78f161a449f3d6b3c55caa850613664db7d50c Mon Sep 17 00:00:00 2001 From: fredfortier Date: Thu, 7 Dec 2017 00:20:27 -0500 Subject: [PATCH 20/52] BLD: more live trading testing and added s3 stats output --- catalyst/examples/mean_reversion_simple.py | 2 +- catalyst/exchange/exchange_algorithm.py | 31 +++++++++++----------- catalyst/exchange/exchange_blotter.py | 19 +++++++++++-- catalyst/exchange/stats_utils.py | 8 +++--- etc/requirements.txt | 2 +- 5 files changed, 39 insertions(+), 23 deletions(-) diff --git a/catalyst/examples/mean_reversion_simple.py b/catalyst/examples/mean_reversion_simple.py index db0a90c3..da49e542 100644 --- a/catalyst/examples/mean_reversion_simple.py +++ b/catalyst/examples/mean_reversion_simple.py @@ -279,5 +279,5 @@ if __name__ == '__main__': base_currency='eth', live_graph=False, simulate_orders=False, - stats_output='s3://something' + stats_output=None ) diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index 013fc618..9745a876 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -625,6 +625,21 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): ) )) + today = pd.to_datetime('today', utc=True) + daily_stats = self.prepare_period_stats( + start_dt=today, + end_dt=pd.Timestamp.utcnow() + ) + save_algo_object( + algo_name=self.algo_namespace, + key=today.strftime('%Y-%m-%d'), + obj=daily_stats, + rel_path='daily_perf' + ) + except Exception as e: + log.warn('unable to calculate performance: {}'.format(e)) + + try: if self.stats_output is not None: if 's3://' in self.stats_output: stats_to_s3( @@ -638,22 +653,8 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): raise ValueError( 'Only S3 stats output is supported for now.' ) - - today = pd.to_datetime('today', utc=True) - daily_stats = self.prepare_period_stats( - start_dt=today, - end_dt=pd.Timestamp.utcnow() - ) - save_algo_object( - algo_name=self.algo_namespace, - key=today.strftime('%Y-%m-%d'), - obj=daily_stats, - rel_path='daily_perf' - ) - except Exception as e: - log.warn('unable to calculate performance: {}'.format(e)) - + log.warn('unable save stats: {}'.format(e)) # TODO: pickle does not seem to work in python 3 try: save_algo_object( diff --git a/catalyst/exchange/exchange_blotter.py b/catalyst/exchange/exchange_blotter.py index 79757221..df8b352d 100644 --- a/catalyst/exchange/exchange_blotter.py +++ b/catalyst/exchange/exchange_blotter.py @@ -147,6 +147,9 @@ class ExchangeBlotter(Blotter): TradingPair: TradingPairFeeSchedule() } + self.retry_delay = 5 + self.retry_check_open_orders = 5 + def exchange_order(self, asset, amount, style=None, attempt_index=0): try: exchange = self.exchanges[asset.exchange] @@ -205,13 +208,25 @@ class ExchangeBlotter(Blotter): for order in self.open_orders[asset]: log.debug('found open order: {}'.format(order.id)) - order, executed_price = exchange.get_order(order.id, asset) + new_order, executed_price = exchange.get_order(order.id, asset) log.debug( 'got updated order {} {}'.format( - order, executed_price + new_order, executed_price ) ) + order.status = new_order.status + if order.status == ORDER_STATUS.FILLED: + order.commission = new_order.commission + if order.amount != new_order.amount: + log.warn( + 'executed order amount {} differs ' + 'from original'.format( + new_order.amount, order.amount + ) + ) + order.amount = new_order.amount + transaction = Transaction( asset=order.asset, amount=order.amount, diff --git a/catalyst/exchange/stats_utils.py b/catalyst/exchange/stats_utils.py index 77f4d283..3b486b77 100644 --- a/catalyst/exchange/stats_utils.py +++ b/catalyst/exchange/stats_utils.py @@ -2,8 +2,11 @@ import numbers import numpy as np import pandas as pd +import boto3 import time +s3 = boto3.resource('s3') + def trend_direction(series): if series[-1] is np.nan or series[-1] is np.nan: @@ -212,12 +215,9 @@ def get_csv_stats(df, recorded_cols=None): def stats_to_s3(uri, df, algo_namespace, recorded_cols=None): - import boto3 - s3 = boto3.resource('s3') - bytes_to_write = get_csv_stats(df, recorded_cols=recorded_cols) - timestr = time.strftime('%Y%m%d-%H%M%S') + timestr = time.strftime('%Y%m%d') parts = uri.split('//') obj = s3.Object(parts[1], 'stats/{}-{}.csv'.format( diff --git a/etc/requirements.txt b/etc/requirements.txt index be375309..1b44dd86 100644 --- a/etc/requirements.txt +++ b/etc/requirements.txt @@ -82,4 +82,4 @@ tables==3.3.0 #Catalyst dependencies ccxt==1.10.283 - +boto3=1.4.8 \ No newline at end of file From 87428b299fae3eda18747e419427c4e4494a5c29 Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Thu, 7 Dec 2017 12:38:50 -0800 Subject: [PATCH 21/52] fixed requirements --- etc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/requirements.txt b/etc/requirements.txt index 1b44dd86..5aa8ccb2 100644 --- a/etc/requirements.txt +++ b/etc/requirements.txt @@ -82,4 +82,4 @@ tables==3.3.0 #Catalyst dependencies ccxt==1.10.283 -boto3=1.4.8 \ No newline at end of file +boto3==1.4.8 From 54dcc58ee83d7e783e64fc611072d0be2c7b00c7 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Thu, 7 Dec 2017 20:26:37 -0500 Subject: [PATCH 22/52] BLD: improved stats display to better support multiple assets per algo --- catalyst/examples/mean_reversion_simple.py | 9 +- catalyst/exchange/exchange_algorithm.py | 6 +- catalyst/exchange/exchange_blotter.py | 4 +- catalyst/exchange/stats_utils.py | 130 ++++++++++++++++----- 4 files changed, 109 insertions(+), 40 deletions(-) diff --git a/catalyst/examples/mean_reversion_simple.py b/catalyst/examples/mean_reversion_simple.py index da49e542..2027cdc8 100644 --- a/catalyst/examples/mean_reversion_simple.py +++ b/catalyst/examples/mean_reversion_simple.py @@ -97,10 +97,9 @@ def handle_data(context, data): # the record() method to save it. This data will be available as # a parameter of the analyze() function for further analysis. record( - price=price, - volume=current['volume'], - price_change=price_change, - rsi=rsi[-1], + volume=(context.market, current['volume']), + price_change=(context.market, price_change), + rsi=(context.market, rsi[-1]), cash=cash ) @@ -278,6 +277,6 @@ if __name__ == '__main__': algo_namespace=NAMESPACE, base_currency='eth', live_graph=False, - simulate_orders=False, + simulate_orders=True, stats_output=None ) diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index 9745a876..7042bde4 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -614,12 +614,12 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): self.add_exposure_stats(frame_stats) - print_df = pd.DataFrame(list(self.frame_stats)) + # print_df = pd.DataFrame(list(self.frame_stats)) log.info( 'statistics for the last {stats_minutes} minutes:\n{stats}'.format( stats_minutes=self.stats_minutes, stats=get_pretty_stats( - df=print_df, + stats=self.frame_stats, recorded_cols=recorded_cols, num_rows=self.stats_minutes ) @@ -644,7 +644,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): if 's3://' in self.stats_output: stats_to_s3( uri=self.stats_output, - df=print_df, + stats=self.frame_stats, algo_namespace=self.algo_namespace, recorded_cols=recorded_cols, ) diff --git a/catalyst/exchange/exchange_blotter.py b/catalyst/exchange/exchange_blotter.py index df8b352d..4ce45fce 100644 --- a/catalyst/exchange/exchange_blotter.py +++ b/catalyst/exchange/exchange_blotter.py @@ -6,7 +6,7 @@ from logbook import Logger from catalyst.constants import LOG_LEVEL from catalyst.exchange.exchange_errors import ExchangeRequestError, \ - ExchangePortfolioDataError, OrphanOrderError, ExchangeTransactionError + ExchangePortfolioDataError, ExchangeTransactionError from catalyst.finance.blotter import Blotter from catalyst.finance.commission import CommissionModel from catalyst.finance.order import ORDER_STATUS @@ -175,6 +175,8 @@ class ExchangeBlotter(Blotter): @expect_types(asset=TradingPair) def order(self, asset, amount, style, order_id=None): + log.debug('ordering {} {}'.format(amount, asset.symbol)) + if self.simulate_orders: return super(ExchangeBlotter, self).order( asset, amount, style, order_id diff --git a/catalyst/exchange/stats_utils.py b/catalyst/exchange/stats_utils.py index 3b486b77..8a34d533 100644 --- a/catalyst/exchange/stats_utils.py +++ b/catalyst/exchange/stats_utils.py @@ -1,10 +1,13 @@ import numbers +import copy import numpy as np import pandas as pd import boto3 import time +from catalyst.assets._assets import TradingPair + s3 = boto3.resource('s3') @@ -123,50 +126,115 @@ def vwap(df): return ret -def format_positions(positions): - parts = [] - for position in positions: - msg = '{amount:.2f}{base} cost basis {cost_basis:.8f}{quote}'.format( - amount=position['amount'], - base=position['sid'].base_currency, - cost_basis=position['cost_basis'], - quote=position['sid'].quote_currency - ) - parts.append(msg) - return ', '.join(parts) +def set_position_row(row, position_index, recorded_cols=None): + """ + Apply the position data as individual columns. + Parameters + ---------- + row: dict[str, Object] + position_index: int + recorded_cols: list[str] + If a recorded_col contains a tuple which first value is an asset + matching a position, its value will be displayed with the + position and not in the index. -def prepare_stats(df, recorded_cols=None): - columns = ['starting_cash', 'ending_cash', 'portfolio_value', - 'pnl', 'long_exposure', 'short_exposure', 'orders', - 'transactions', 'positions'] + Returns + ------- + + """ + position = row['positions'][position_index] + + asset = position['sid'] + row['symbol'] = asset.symbol + + columns = ['amount', 'cost_basis', 'last_sale_price'] + for column in columns: + row[column] = position[column] + + columns.insert(0, 'symbol') + if recorded_cols is not None: + for column in recorded_cols[:]: + value = row[column] + if type(value) in [list, tuple] and \ + isinstance(value[0], TradingPair) and asset == value[0]: + row[column] = value[1] + + columns.append(column) + # Removing the asset specific entries + recorded_cols.remove(column) + + return columns + + +def prepare_stats(stats, recorded_cols=None): + """ + Prepare the stats DataFrame for user-friendly output. + + Parameters + ---------- + stats: list[Object] + recorded_cols: list[str] + + Returns + ------- + + """ + position_cols = None + + # Using a copy since we are adding rows inside the loop. + for row_index, row_data in enumerate(list(stats)): + if len(row_data['positions']) == 1: + row = stats[row_index] + columns = set_position_row(row, 0, recorded_cols) + + elif len(row_data['positions']) > 1: + for pos_index, position in enumerate(row_data['positions']): + if pos_index > 0: + row = row_data + stats.append(row) + + else: + row = stats[row_index] + + columns = set_position_row(row, pos_index, recorded_cols) + + else: + break + + if position_cols is None: + position_cols = columns + + df = pd.DataFrame(list(stats)) + + index_cols = [ + 'period_close', 'starting_cash', 'ending_cash', 'portfolio_value', + 'pnl', 'long_exposure', 'short_exposure', 'orders', 'transactions', + ] if recorded_cols is not None: for column in recorded_cols: - columns.append(column) - - df = df.copy(True) - - df.set_index('period_close', drop=True, inplace=True) - df.dropna(axis=1, how='all', inplace=True) + index_cols.append(column) df['orders'] = df['orders'].apply(lambda orders: len(orders)) df['transactions'] = df['transactions'].apply( lambda transactions: len(transactions) ) - df['positions'] = df['positions'].apply(format_positions) - return df, columns + df.set_index(index_cols, drop=True, inplace=True) + df.dropna(axis=1, how='all', inplace=True) + + return df, position_cols -def get_pretty_stats(df, recorded_cols=None, num_rows=10): +def get_pretty_stats(stats, recorded_cols=None, num_rows=10): """ Format and print the last few rows of a statistics DataFrame. See the pyfolio project for the data structure. Parameters ---------- - df: pd.DataFrame + stats: list[Object] num_rows: int Returns @@ -174,7 +242,7 @@ def get_pretty_stats(df, recorded_cols=None, num_rows=10): str """ - df, columns = prepare_stats(df, recorded_cols=recorded_cols) + df, columns = prepare_stats(stats, recorded_cols=recorded_cols) pd.set_option('display.expand_frame_repr', False) pd.set_option('precision', 3) @@ -191,21 +259,21 @@ def get_pretty_stats(df, recorded_cols=None, num_rows=10): ) -def get_csv_stats(df, recorded_cols=None): +def get_csv_stats(stats, recorded_cols=None): """ Create a CSV buffer from the stats DataFrame. Parameters ---------- path: str - df: pd.DataFrame + stats: list[Object] recorded_cols: list[str] Returns ------- """ - df, columns = prepare_stats(df, recorded_cols=recorded_cols) + df, columns = prepare_stats(stats, recorded_cols=recorded_cols) return df.to_csv( None, @@ -214,8 +282,8 @@ def get_csv_stats(df, recorded_cols=None): ).encode() -def stats_to_s3(uri, df, algo_namespace, recorded_cols=None): - bytes_to_write = get_csv_stats(df, recorded_cols=recorded_cols) +def stats_to_s3(uri, stats, algo_namespace, recorded_cols=None): + bytes_to_write = get_csv_stats(stats, recorded_cols=recorded_cols) timestr = time.strftime('%Y%m%d') From 2e7aabd97372ecc334e194cf1bbbeeeef94f371b Mon Sep 17 00:00:00 2001 From: fredfortier Date: Thu, 7 Dec 2017 22:14:49 -0500 Subject: [PATCH 23/52] BLD: tested stats with multiple assets --- catalyst/examples/mean_reversion_simple.py | 2 +- catalyst/exchange/exchange_algorithm.py | 2 +- catalyst/exchange/exchange_blotter.py | 3 + catalyst/exchange/stats_utils.py | 88 +++++++++++++--------- 4 files changed, 57 insertions(+), 38 deletions(-) diff --git a/catalyst/examples/mean_reversion_simple.py b/catalyst/examples/mean_reversion_simple.py index 2027cdc8..450a73f7 100644 --- a/catalyst/examples/mean_reversion_simple.py +++ b/catalyst/examples/mean_reversion_simple.py @@ -96,13 +96,13 @@ def handle_data(context, data): # Now that we've collected all current data for this frame, we use # the record() method to save it. This data will be available as # a parameter of the analyze() function for further analysis. + record( volume=(context.market, current['volume']), price_change=(context.market, price_change), rsi=(context.market, rsi[-1]), cash=cash ) - # We are trying to avoid over-trading by limiting our trades to # one per day. if context.traded_today: diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index 7042bde4..147d87e4 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -326,7 +326,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): self.retry_order = 2 self.retry_delay = 5 - self.stats_minutes = 5 + self.stats_minutes = 20 super(ExchangeTradingAlgorithmLive, self).__init__(*args, **kwargs) diff --git a/catalyst/exchange/exchange_blotter.py b/catalyst/exchange/exchange_blotter.py index 4ce45fce..7b89f9b9 100644 --- a/catalyst/exchange/exchange_blotter.py +++ b/catalyst/exchange/exchange_blotter.py @@ -176,6 +176,9 @@ class ExchangeBlotter(Blotter): @expect_types(asset=TradingPair) def order(self, asset, amount, style, order_id=None): log.debug('ordering {} {}'.format(amount, asset.symbol)) + if amount == 0: + log.warn('skipping 0 amount orders') + return None if self.simulate_orders: return super(ExchangeBlotter, self).order( diff --git a/catalyst/exchange/stats_utils.py b/catalyst/exchange/stats_utils.py index 8a34d533..03df1740 100644 --- a/catalyst/exchange/stats_utils.py +++ b/catalyst/exchange/stats_utils.py @@ -126,15 +126,15 @@ def vwap(df): return ret -def set_position_row(row, position_index, recorded_cols=None): +def set_position_row(row, asset, asset_values=list()): """ Apply the position data as individual columns. Parameters ---------- row: dict[str, Object] - position_index: int - recorded_cols: list[str] + asset: TradingPair + asset_values: list[str] If a recorded_col contains a tuple which first value is an asset matching a position, its value will be displayed with the position and not in the index. @@ -143,32 +143,31 @@ def set_position_row(row, position_index, recorded_cols=None): ------- """ - position = row['positions'][position_index] - - asset = position['sid'] + asset_cols = ['symbol'] row['symbol'] = asset.symbol + position = next((p for p in row['positions'] if p['sid'] == asset), None) + columns = ['amount', 'cost_basis', 'last_sale_price'] for column in columns: - row[column] = position[column] + if position is not None: + row[column] = position[column] - columns.insert(0, 'symbol') + else: + row[column] = 0 - if recorded_cols is not None: - for column in recorded_cols[:]: - value = row[column] - if type(value) in [list, tuple] and \ - isinstance(value[0], TradingPair) and asset == value[0]: - row[column] = value[1] + asset_cols.append(column) - columns.append(column) - # Removing the asset specific entries - recorded_cols.remove(column) + values = asset_values[asset] if asset in asset_values else list() + for column in values: + row[column] = values[column] - return columns + asset_cols.append(column) + + return asset_cols -def prepare_stats(stats, recorded_cols=None): +def prepare_stats(stats, recorded_cols=list()): """ Prepare the stats DataFrame for user-friendly output. @@ -181,30 +180,43 @@ def prepare_stats(stats, recorded_cols=None): ------- """ - position_cols = None + asset_cols = list() # Using a copy since we are adding rows inside the loop. for row_index, row_data in enumerate(list(stats)): - if len(row_data['positions']) == 1: - row = stats[row_index] - columns = set_position_row(row, 0, recorded_cols) + assets = [p['sid'] for p in row_data['positions']] - elif len(row_data['positions']) > 1: - for pos_index, position in enumerate(row_data['positions']): - if pos_index > 0: - row = row_data + asset_values = dict() + for column in recorded_cols[:]: + value = row_data[column] + if type(value) is dict: + for asset in value: + if not isinstance(asset, TradingPair): + break + + if asset not in assets: + assets.append(asset) + + if asset not in asset_values: + asset_values[asset] = dict() + + asset_values[asset][column] = value[asset] + + if len(assets) == 1: + row = stats[row_index] + asset_cols = set_position_row(row, assets[0], asset_values) + + elif len(assets) > 1: + for asset_index, asset in enumerate(assets): + if asset_index > 0: + row = copy.deepcopy(row_data) stats.append(row) else: row = stats[row_index] - columns = set_position_row(row, pos_index, recorded_cols) - - else: - break - - if position_cols is None: - position_cols = columns + asset_cols = set_position_row(row, assets[asset_index], + asset_values) df = pd.DataFrame(list(stats)) @@ -212,6 +224,9 @@ def prepare_stats(stats, recorded_cols=None): 'period_close', 'starting_cash', 'ending_cash', 'portfolio_value', 'pnl', 'long_exposure', 'short_exposure', 'orders', 'transactions', ] + + # Removing the asset specific entries + recorded_cols = [x for x in recorded_cols if x not in asset_cols] if recorded_cols is not None: for column in recorded_cols: index_cols.append(column) @@ -223,8 +238,9 @@ def prepare_stats(stats, recorded_cols=None): df.set_index(index_cols, drop=True, inplace=True) df.dropna(axis=1, how='all', inplace=True) + df.sort_index(inplace=True) - return df, position_cols + return df, asset_cols def get_pretty_stats(stats, recorded_cols=None, num_rows=10): @@ -245,7 +261,7 @@ def get_pretty_stats(stats, recorded_cols=None, num_rows=10): df, columns = prepare_stats(stats, recorded_cols=recorded_cols) pd.set_option('display.expand_frame_repr', False) - pd.set_option('precision', 3) + pd.set_option('precision', 8) pd.set_option('display.width', 1000) pd.set_option('display.max_colwidth', 1000) From 1c03d837cc935b53515df1dbeb6b5b0e448f335d Mon Sep 17 00:00:00 2001 From: fredfortier Date: Thu, 7 Dec 2017 22:26:01 -0500 Subject: [PATCH 24/52] BUG: fixed issue with stats output --- catalyst/exchange/stats_utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/catalyst/exchange/stats_utils.py b/catalyst/exchange/stats_utils.py index 03df1740..43e9e5ec 100644 --- a/catalyst/exchange/stats_utils.py +++ b/catalyst/exchange/stats_utils.py @@ -1,3 +1,4 @@ +import csv import numbers import copy @@ -294,7 +295,8 @@ def get_csv_stats(stats, recorded_cols=None): return df.to_csv( None, columns=columns, - encoding='utf-8', + # encoding='utf-8', + quoting=csv.QUOTE_NONNUMERIC ).encode() From b644c947e365486c65eb3bac962ce75fd6514d22 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Thu, 7 Dec 2017 23:01:12 -0500 Subject: [PATCH 25/52] BUG: fixed issue with stats output --- catalyst/exchange/stats_utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/catalyst/exchange/stats_utils.py b/catalyst/exchange/stats_utils.py index 43e9e5ec..ec89a340 100644 --- a/catalyst/exchange/stats_utils.py +++ b/catalyst/exchange/stats_utils.py @@ -183,6 +183,7 @@ def prepare_stats(stats, recorded_cols=list()): """ asset_cols = list() + stats = copy.deepcopy(list(stats)) # Using a copy since we are adding rows inside the loop. for row_index, row_data in enumerate(list(stats)): assets = [p['sid'] for p in row_data['positions']] @@ -219,7 +220,7 @@ def prepare_stats(stats, recorded_cols=list()): asset_cols = set_position_row(row, assets[asset_index], asset_values) - df = pd.DataFrame(list(stats)) + df = pd.DataFrame(stats) index_cols = [ 'period_close', 'starting_cash', 'ending_cash', 'portfolio_value', @@ -239,7 +240,7 @@ def prepare_stats(stats, recorded_cols=list()): df.set_index(index_cols, drop=True, inplace=True) df.dropna(axis=1, how='all', inplace=True) - df.sort_index(inplace=True) + df.sort_index(axis=0, level=0, inplace=True) return df, asset_cols From 619fbfc6eadd1c77a0c80f37973abe74ee658db7 Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Thu, 7 Dec 2017 22:32:17 -0700 Subject: [PATCH 26/52] DOC: PEP8 simple_universe.py & added to example_algos.html --- catalyst/examples/simple_universe.py | 141 ++++++++++++--------- docs/source/example-algos.rst | 176 +++++++++++++++++++++++++++ 2 files changed, 261 insertions(+), 56 deletions(-) diff --git a/catalyst/examples/simple_universe.py b/catalyst/examples/simple_universe.py index ff79c763..a0abc535 100644 --- a/catalyst/examples/simple_universe.py +++ b/catalyst/examples/simple_universe.py @@ -2,73 +2,105 @@ Requires Catalyst version 0.3.0 or above Tested on Catalyst version 0.3.3 -These example aims to provide and easy way for users to learn how to collect data from the different exchanges. -You simply need to specify the exchange and the market that you want to focus on. -You will all see how to create a universe and filter it base on the exchange and the market you desire. +This example aims to provide an easy way for users to learn how to +collect data from any given exchange and select a subset of the available +currency pairs for trading. You simply need to specify the exchange and +the market (base_currency) that you want to focus on. You will then see +how to create a universe of assets, and filter it based the market you +desire. -The example prints out the closing price of all the pairs for a given market-exchange every 30 minutes. -The example also contains the ohlcv minute data for the past seven days which could be used to create indicators -Use this as the backbone to create your own trading strategies. +The example prints out the closing price of all the pairs for a given +market in a given exchange every 30 minutes. The example also contains +the OHLCV data with minute-resolution for the past seven days which +could be used to create indicators. Use this code as the backbone to +create your own trading strategy. + +The lookback_date variable is used to ensure data for a coin existed on +the lookback period specified. + +To run, execute the following two commands in a terminal (inside catalyst +environment). The first one retrieves all the pricing data needed for this +script to run (only needs to be run once), and the second one executes this +script with the parameters specified in the run_algorithm() call at the end +of the file: + +catalyst ingest-exchange -x bitfinex -f minute + +python simple_universe.py -Variables lookback date and date are used to ensure data for a coin existed on the lookback period specified. """ +from datetime import timedelta import numpy as np import pandas as pd -from datetime import timedelta + from catalyst import run_algorithm from catalyst.exchange.exchange_utils import get_exchange_symbols - -from catalyst.api import ( - symbols, -) +from catalyst.api import (symbols, ) def initialize(context): - context.i = -1 # counts the minutes - context.exchange = context.exchanges.values()[0].name.lower() # exchange name - context.base_currency = context.exchanges.values()[0].base_currency.lower() # market base currency + context.i = -1 # minute counter + context.exchange = context.exchanges.values()[0].name.lower() + context.base_currency = context.exchanges.values()[0].base_currency.lower() def handle_data(context, data): context.i += 1 lookback_days = 7 # 7 days - # current date formatted into a string - today = data.current_dt - date, time = today.strftime('%Y-%m-%d %H:%M:%S').split(' ') - lookback_date = today - timedelta(days=lookback_days) # subtract the amount of days specified in lookback - lookback_date = lookback_date.strftime('%Y-%m-%d %H:%M:%S').split(' ')[0] # get only the date as a string + # current date & time in each iteration formatted into a string + now = data.current_dt + date, time = now.strftime('%Y-%m-%d %H:%M:%S').split(' ') + lookback_date = now - timedelta(days=lookback_days) + # keep only the date as a string, discard the time + lookback_date = lookback_date.strftime('%Y-%m-%d %H:%M:%S').split(' ')[0] - # update universe everyday - new_day = 60 * 24 # assuming data_frequency='minute' - if not context.i % new_day: + one_day_in_minutes = 1440 # 60 * 24 assumes data_frequency='minute' + # update universe everyday at midnight + if not context.i % one_day_in_minutes: context.universe = universe(context, lookback_date, date) # get data every 30 minutes minutes = 30 - one_day_in_minutes = 1440 # 1440 assumes data_frequency='minute' - lookback = one_day_in_minutes / minutes * lookback_days # get N lookback_days of history data + # get lookback_days of history data: that is 'lookback' number of bins + lookback = one_day_in_minutes / minutes * lookback_days if not context.i % minutes and context.universe: # we iterate for every pair in the current universe for coin in context.coins: pair = str(coin.symbol) - # 30 minute interval ohlcv data (the standard data required for candlestick or indicators/signals) - # 30T means 30 minutes re-sampling of one minute data. change to your desire time interval. - opened = fill(data.history(coin, 'open', bar_count=lookback, frequency='30T')).values - high = fill(data.history(coin, 'high', bar_count=lookback, frequency='30T')).values - low = fill(data.history(coin, 'low', bar_count=lookback, frequency='30T')).values - close = fill(data.history(coin, 'price', bar_count=lookback, frequency='30T')).values - volume = fill(data.history(coin, 'volume', bar_count=lookback, frequency='30T')).values + # Get 30 minute interval OHLCV data. This is the standard data + # required for candlestick or indicators/signals. Return Pandas + # DataFrames. 30T means 30-minute re-sampling of one minute data. + # Adjust it to your desired time interval as needed. + opened = fill(data.history(coin, 'open', + bar_count=lookback, frequency='30T')).values + high = fill(data.history(coin, 'high', + bar_count=lookback, frequency='30T')).values + low = fill(data.history(coin, 'low', + bar_count=lookback, frequency='30T')).values + close = fill(data.history(coin, 'price', + bar_count=lookback, frequency='30T')).values + volume = fill(data.history(coin, 'volume', + bar_count=lookback, frequency='30T')).values - # close[-1] is the equivalent to current price + # close[-1] is the last value in the set, which is the equivalent + # to current price (as in the most recent value) # displays the minute price for each pair every 30 minutes - print(today, pair, opened[-1], high[-1], low[-1], close[-1], volume[-1]) + print('{now}: {pair} -\tO:{o},\tH:{h},\tL:{c},\tC{c},\tV:{v}'.format( + now=now, + pair=pair, + o=opened[-1], + h=high[-1], + l=low[-1], + c=close[-1], + v=volume[-1], + )) - # ---------------------------------------------------------------------------------------------------------- - # -------------------------------------- Insert Your Strategy Here ----------------------------------------- - # ---------------------------------------------------------------------------------------------------------- + # ------------------------------------------------------------- + # --------------- Insert Your Strategy Here ------------------- + # ------------------------------------------------------------- def analyze(context=None, results=None): @@ -78,23 +110,22 @@ def analyze(context=None, results=None): # Get the universe for a given exchange and a given base_currency market # Example: Poloniex BTC Market def universe(context, lookback_date, current_date): - json_symbols = get_exchange_symbols(context.exchange) # get all the pairs for the exchange - universe_df = pd.DataFrame.from_dict(json_symbols).transpose().astype(str) # convert into a dataframe - universe_df['base_currency'] = universe_df.apply(lambda row: row.symbol.split('_')[1], - axis=1) - universe_df['market_currency'] = universe_df.apply(lambda row: row.symbol.split('_')[0], - axis=1) + # get all the pairs for the given exchange + json_symbols = get_exchange_symbols(context.exchange) + # convert into a DataFrame for easier processing + df = pd.DataFrame.from_dict(json_symbols).transpose().astype(str) + df['base_currency'] = df.apply(lambda row: row.symbol.split('_')[1],axis=1) + df['market_currency'] = df.apply(lambda row: row.symbol.split('_')[0],axis=1) - # Filter all the exchange pairs to only the ones for a give base currency - universe_df = universe_df[universe_df['base_currency'] == context.base_currency] + # Filter all the pairs to get only the ones for a given base_currency + df = df[df['base_currency'] == context.base_currency] # Filter all the pairs to ensure that pair existed in the current date range - universe_df = universe_df[universe_df.start_date < lookback_date] - universe_df = universe_df[universe_df.end_daily >= current_date] - context.coins = symbols(*universe_df.symbol) # convert all the pairs to symbols + df = df[df.start_date < lookback_date] + df = df[df.end_daily >= current_date] + context.coins = symbols(*df.symbol) # convert all the pairs to symbols - # print(universe_df.symbol.tolist()) - return universe_df.symbol.tolist() + return df.symbol.tolist() # Replace all NA, NAN or infinite values with its nearest value @@ -102,7 +133,9 @@ def fill(series): if isinstance(series, pd.Series): return series.replace([np.inf, -np.inf], np.nan).ffill().bfill() elif isinstance(series, np.ndarray): - return pd.Series(series).replace([np.inf, -np.inf], np.nan).ffill().bfill().values + return pd.Series(series).replace( + [np.inf, -np.inf], np.nan + ).ffill().bfill().values else: return series @@ -112,7 +145,7 @@ if __name__ == '__main__': end_date = pd.to_datetime('2017-11-13', utc=True) performance = run_algorithm(start=start_date, end=end_date, - capital_base=100.0, # amount of base_currency, not always in dollars unless usd + capital_base=100.0, # amount of base_currency initialize=initialize, handle_data=handle_data, analyze=analyze, @@ -123,7 +156,3 @@ if __name__ == '__main__': live_graph=False, algo_namespace='simple_universe') -""" -Run in Terminal (inside catalyst environment): -python simple_universe.py -""" diff --git a/docs/source/example-algos.rst b/docs/source/example-algos.rst index 550d8b58..ff5d5ea7 100644 --- a/docs/source/example-algos.rst +++ b/docs/source/example-algos.rst @@ -31,6 +31,11 @@ Overview `two-part video tutorial `_ to show how to get started in backtesting and live trading with Catalyst. +- :ref:`Simple Universe `: This code provides the 'universe' + of available trading pairs on a given exchange on any given day. You can use + this code to dynamically select which currency pairs you want to trade each + day of your strategy. This example does not make any trades. + - :ref:`Portfolio Optimization `: Use this code to execute a portfolio optimization model. This strategy will select the portfolio with the maximum Sharpe Ratio. The parameters are set to use 180 @@ -753,6 +758,177 @@ implemented after the video was recorded, which executes the orders at slighlty different prices, but resulting in significant changes in performance of our strategy. +.. _simple_universe: + +Simple Universe +~~~~~~~~~~~~~~~ + +Source code: `examples/simple_universe.py `_ + +This example aims to provide an easy way for users to learn how to +collect data from any given exchange and select a subset of the available +currency pairs for trading. You simply need to specify the exchange and +the market (base_currency) that you want to focus on. You will then see +how to create a universe of assets, and filter it based the market you +desire. + +The example prints out the closing price of all the pairs for a given +market in a given exchange every 30 minutes. The example also contains +the OHLCV data with minute-resolution for the past seven days which +could be used to create indicators. Use this code as the backbone to +create your own trading strategy. + +The lookback_date variable is used to ensure data for a coin existed on +the lookback period specified. + +To run, execute the following two commands in a terminal (inside catalyst +environment). The first one retrieves all the pricing data needed for this +script to run (only needs to be run once), and the second one executes this +script with the parameters specified in the run_algorithm() call at the end +of the file: + +.. code-block:: bash + + catalyst ingest-exchange -x bitfinex -f minute + +.. code-block:: bash + + python simple_universe.py + +Credits: This code was originally submitted by `Abner Ayala-Acevedo +`_. Thank you! + +.. code-block:: python + + from datetime import timedelta + + import numpy as np + import pandas as pd + + from catalyst import run_algorithm + from catalyst.exchange.exchange_utils import get_exchange_symbols + from catalyst.api import (symbols, ) + + + def initialize(context): + context.i = -1 # minute counter + context.exchange = context.exchanges.values()[0].name.lower() + context.base_currency = context.exchanges.values()[0].base_currency.lower() + + + def handle_data(context, data): + context.i += 1 + lookback_days = 7 # 7 days + + # current date & time in each iteration formatted into a string + now = data.current_dt + date, time = now.strftime('%Y-%m-%d %H:%M:%S').split(' ') + lookback_date = now - timedelta(days=lookback_days) + # keep only the date as a string, discard the time + lookback_date = lookback_date.strftime('%Y-%m-%d %H:%M:%S').split(' ')[0] + + one_day_in_minutes = 1440 # 60 * 24 assumes data_frequency='minute' + # update universe everyday at midnight + if not context.i % one_day_in_minutes: + context.universe = universe(context, lookback_date, date) + + # get data every 30 minutes + minutes = 30 + # get lookback_days of history data: that is 'lookback' number of bins + lookback = one_day_in_minutes / minutes * lookback_days + if not context.i % minutes and context.universe: + # we iterate for every pair in the current universe + for coin in context.coins: + pair = str(coin.symbol) + + # Get 30 minute interval OHLCV data. This is the standard data + # required for candlestick or indicators/signals. Return Pandas + # DataFrames. 30T means 30-minute re-sampling of one minute data. + # Adjust it to your desired time interval as needed. + opened = fill(data.history(coin, 'open', + bar_count=lookback, frequency='30T')).values + high = fill(data.history(coin, 'high', + bar_count=lookback, frequency='30T')).values + low = fill(data.history(coin, 'low', + bar_count=lookback, frequency='30T')).values + close = fill(data.history(coin, 'price', + bar_count=lookback, frequency='30T')).values + volume = fill(data.history(coin, 'volume', + bar_count=lookback, frequency='30T')).values + + # close[-1] is the last value in the set, which is the equivalent + # to current price (as in the most recent value) + # displays the minute price for each pair every 30 minutes + print('{now}: {pair} -\tO:{o},\tH:{h},\tL:{c},\tC{c},\tV:{v}'.format( + now=now, + pair=pair, + o=opened[-1], + h=high[-1], + l=low[-1], + c=close[-1], + v=volume[-1], + )) + + # ------------------------------------------------------------- + # --------------- Insert Your Strategy Here ------------------- + # ------------------------------------------------------------- + + + def analyze(context=None, results=None): + pass + + + # Get the universe for a given exchange and a given base_currency market + # Example: Poloniex BTC Market + def universe(context, lookback_date, current_date): + # get all the pairs for the given exchange + json_symbols = get_exchange_symbols(context.exchange) + # convert into a DataFrame for easier processing + df = pd.DataFrame.from_dict(json_symbols).transpose().astype(str) + df['base_currency'] = df.apply(lambda row: row.symbol.split('_')[1],axis=1) + df['market_currency'] = df.apply(lambda row: row.symbol.split('_')[0],axis=1) + + # Filter all the pairs to get only the ones for a given base_currency + df = df[df['base_currency'] == context.base_currency] + + # Filter all the pairs to ensure that pair existed in the current date range + df = df[df.start_date < lookback_date] + df = df[df.end_daily >= current_date] + context.coins = symbols(*df.symbol) # convert all the pairs to symbols + + return df.symbol.tolist() + + + # Replace all NA, NAN or infinite values with its nearest value + def fill(series): + if isinstance(series, pd.Series): + return series.replace([np.inf, -np.inf], np.nan).ffill().bfill() + elif isinstance(series, np.ndarray): + return pd.Series(series).replace( + [np.inf, -np.inf], np.nan + ).ffill().bfill().values + else: + return series + + + if __name__ == '__main__': + start_date = pd.to_datetime('2017-11-10', utc=True) + end_date = pd.to_datetime('2017-11-13', utc=True) + + performance = run_algorithm(start=start_date, end=end_date, + capital_base=100.0, # amount of base_currency + initialize=initialize, + handle_data=handle_data, + analyze=analyze, + exchange_name='bitfinex', + data_frequency='minute', + base_currency='btc', + live=False, + live_graph=False, + algo_namespace='simple_universe') + + + .. _portfolio_optimization: Portfolio Optimization From eb5d55478daea452da9b88896ce8fbdb1f8a72a1 Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Thu, 7 Dec 2017 23:04:54 -0700 Subject: [PATCH 27/52] MAINT: added CCXT requirement to Conda yml environment --- etc/python2.7-environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/etc/python2.7-environment.yml b/etc/python2.7-environment.yml index d59c36ae..3959b8df 100644 --- a/etc/python2.7-environment.yml +++ b/etc/python2.7-environment.yml @@ -20,6 +20,7 @@ dependencies: - bcolz==0.12.1 - bottleneck==1.2.1 - chardet==3.0.4 + - ccxt==1.10.319 - click==6.7 - contextlib2==0.5.5 - cycler==0.10.0 From 89f9a1179ea7d65cbf4cb5e01a077139cc127500 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Fri, 8 Dec 2017 15:15:18 -0500 Subject: [PATCH 28/52] BLD: improvements to stats output --- catalyst/exchange/exchange_algorithm.py | 64 +++++++++++++++++++++---- catalyst/exchange/stats_utils.py | 46 ++++++++++++++++-- 2 files changed, 97 insertions(+), 13 deletions(-) diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index 147d87e4..2ce0357f 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -36,7 +36,8 @@ from catalyst.exchange.exchange_utils import save_algo_object, get_algo_object, save_algo_df, group_assets_by_exchange from catalyst.exchange.live_graph_clock import LiveGraphClock from catalyst.exchange.simple_clock import SimpleClock -from catalyst.exchange.stats_utils import get_pretty_stats, stats_to_s3 +from catalyst.exchange.stats_utils import get_pretty_stats, stats_to_s3, \ + stats_to_algo_folder from catalyst.finance.execution import MarketOrder from catalyst.finance.performance.period import calc_period_stats from catalyst.gens.tradesimulation import AlgorithmSimulator @@ -61,6 +62,8 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm): super(ExchangeTradingAlgorithmBase, self).__init__(*args, **kwargs) + self.current_day = None + if self.simulate_orders is None \ and self.sim_params.arena == 'backtest': self.simulate_orders = True @@ -281,6 +284,8 @@ class ExchangeTradingAlgorithmBacktest(ExchangeTradingAlgorithmBase): ) self.frame_stats.append(frame_stats) + self.current_day = data.current_dt.floor('1D') + def _create_stats_df(self): stats = pd.DataFrame(self.frame_stats) stats.set_index('period_close', inplace=True, drop=False) @@ -308,7 +313,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): self.stats_output = kwargs.pop('stats_output', None) self._clock = None - self.frame_stats = deque(maxlen=60) + self.frame_stats = list() self.pnl_stats = get_algo_df(self.algo_namespace, 'pnl_stats') @@ -326,7 +331,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): self.retry_order = 2 self.retry_delay = 5 - self.stats_minutes = 20 + self.stats_minutes = 10 super(ExchangeTradingAlgorithmLive, self).__init__(*args, **kwargs) @@ -438,27 +443,49 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): return self.perf_tracker.get_account(False) def synchronize_portfolio(self, attempt_index=0): + """ + Synchronizes the portfolio tracked by the algorithm to refresh + its current value. + + This includes updating the last_sale_price of all tracked + positions, returning the available cash, and raising error + if the data goes out of sync. + + Parameters + ---------- + attempt_index: int + + Returns + ------- + float + The amount of base currency available for trading. + + float + The total value of all tracked positions. + + """ tracker = self.perf_tracker.position_tracker total_cash = 0.0 total_positions_value = 0.0 try: # Position keys correspond to assets - assets = list(tracker.positions) + positions = self.portfolio.positions + assets = list(positions) exchange_assets = group_assets_by_exchange(assets) for exchange_name in self.exchanges: assets = exchange_assets[exchange_name] \ if exchange_name in exchange_assets else [] exchange_positions = \ - [tracker.positions[asset] for asset in assets] + [positions[asset] for asset in assets] exchange = self.exchanges[exchange_name] # Type: Exchange cash, positions_value = \ exchange.calculate_totals(exchange_positions) total_cash += cash - total_positions_value += total_positions_value + total_positions_value += positions_value for position in exchange_positions: tracker.update_position( @@ -471,6 +498,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): raise ValueError('Cash on exchanges is lower than the algo.') return total_cash, total_positions_value + except ExchangeRequestError as e: log.warn( 'update portfolio attempt {}: {}'.format(attempt_index, e) @@ -574,6 +602,11 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): if not self.is_running: return + # Resetting the frame stats every day to minimize memory footprint + today = data.current_dt.floor('1D') + if self.current_day is not None and today > self.current_day: + self.frame_stats = list() + new_transactions, new_commissions, closed_orders = \ self.blotter.get_transactions(data) @@ -625,7 +658,6 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): ) )) - today = pd.to_datetime('today', utc=True) daily_stats = self.prepare_period_stats( start_dt=today, end_dt=pd.Timestamp.utcnow() @@ -639,6 +671,17 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): except Exception as e: log.warn('unable to calculate performance: {}'.format(e)) + csv_bytes = None + try: + csv_bytes = stats_to_algo_folder( + stats=self.frame_stats, + algo_namespace=self.algo_namespace, + recorded_cols=recorded_cols, + ) + + except Exception as e: + log.warn('unable save stats locally: {}'.format(e)) + try: if self.stats_output is not None: if 's3://' in self.stats_output: @@ -647,14 +690,17 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): stats=self.frame_stats, algo_namespace=self.algo_namespace, recorded_cols=recorded_cols, + bytes_to_write=csv_bytes ) else: raise ValueError( 'Only S3 stats output is supported for now.' ) + except Exception as e: - log.warn('unable save stats: {}'.format(e)) + log.warn('unable save stats externally: {}'.format(e)) + # TODO: pickle does not seem to work in python 3 try: save_algo_object( @@ -665,6 +711,8 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): except Exception as e: log.warn('unable to save minute perfs to disk: {}'.format(e)) + self.current_day = data.current_dt.floor('1D') + @api_method def batch_market_order(self, share_counts): raise NotImplementedError() diff --git a/catalyst/exchange/stats_utils.py b/catalyst/exchange/stats_utils.py index ec89a340..b5a080dc 100644 --- a/catalyst/exchange/stats_utils.py +++ b/catalyst/exchange/stats_utils.py @@ -3,12 +3,15 @@ import numbers import copy import numpy as np +import os import pandas as pd import boto3 import time from catalyst.assets._assets import TradingPair +from catalyst.exchange.exchange_utils import get_algo_folder + s3 = boto3.resource('s3') @@ -183,7 +186,7 @@ def prepare_stats(stats, recorded_cols=list()): """ asset_cols = list() - stats = copy.deepcopy(list(stats)) + stats = copy.deepcopy(stats) # Using a copy since we are adding rows inside the loop. for row_index, row_data in enumerate(list(stats)): assets = [p['sid'] for p in row_data['positions']] @@ -301,18 +304,51 @@ def get_csv_stats(stats, recorded_cols=None): ).encode() -def stats_to_s3(uri, stats, algo_namespace, recorded_cols=None): - bytes_to_write = get_csv_stats(stats, recorded_cols=recorded_cols) +def stats_to_s3(uri, stats, algo_namespace, recorded_cols=None, + folder='catalyst/stats', bytes_to_write=None): + """ + Uploads the performance stats to a S3 bucket. + + Parameters + ---------- + uri: str + stats: list[Object] + algo_namespace: str + recorded_cols: list[str] + folder: str + bytes_to_write: str + Option to reuse bytes instead of re-computing the csv + + Returns + ------- + + """ + if bytes_to_write is None: + bytes_to_write = get_csv_stats(stats, recorded_cols=recorded_cols) timestr = time.strftime('%Y%m%d') parts = uri.split('//') - obj = s3.Object(parts[1], 'stats/{}-{}.csv'.format( - timestr, algo_namespace + obj = s3.Object(parts[1], '{}/{}-{}.csv'.format( + folder, timestr, algo_namespace )) obj.put(Body=bytes_to_write) +def stats_to_algo_folder(stats, algo_namespace, recorded_cols=None): + bytes_to_write = get_csv_stats(stats, recorded_cols=recorded_cols) + + timestr = time.strftime('%Y%m%d') + folder = get_algo_folder(algo_namespace) + + filename = os.path.join(folder, '{}-{}.csv'.format(timestr, 'frames')) + + with open(filename, 'wb') as handle: + handle.write(bytes_to_write) + + return bytes_to_write + + def df_to_string(df): """ Create a formatted str representation of the DataFrame. From ce085e01ecb92375dd14ce552644280f2792f738 Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Fri, 8 Dec 2017 13:18:24 -0700 Subject: [PATCH 29/52] MAINT: PEP8 compliance --- catalyst/__init__.py | 14 +- catalyst/__main__.py | 3 +- catalyst/algorithm.py | 3 +- catalyst/constants.py | 5 +- catalyst/curate/poloniex.py | 277 +++++++++--------- catalyst/data/bundles/__init__.py | 1 - catalyst/data/bundles/base.py | 71 +++-- catalyst/data/bundles/base_pricing.py | 3 + catalyst/data/bundles/core.py | 5 +- catalyst/data/bundles/poloniex.py | 34 +-- catalyst/data/bundles/quandl.py | 27 +- catalyst/data/data_portal.py | 12 +- catalyst/data/dispatch_bar_reader.py | 2 + catalyst/data/loader.py | 97 ++---- catalyst/data/minute_bars.py | 19 +- catalyst/data/us_equity_pricing.py | 18 +- catalyst/examples/buy_and_hodl.py | 2 +- catalyst/examples/buy_btc_simple.py | 34 ++- catalyst/examples/buy_low_sell_high.py | 17 +- catalyst/examples/buy_low_sell_high_live.py | 4 +- catalyst/examples/dual_moving_average.py | 39 ++- catalyst/examples/dual_vwap.py | 10 +- catalyst/examples/mean_reversion_simple.py | 12 +- catalyst/examples/portfolio_optimization.py | 209 +++++++------ catalyst/examples/rsi_profit_target.py | 7 +- catalyst/examples/simple_universe.py | 94 +++--- catalyst/examples/talib_simple.py | 12 +- catalyst/exchange/bitfinex/bitfinex.py | 28 +- catalyst/exchange/bittrex/bittrex.py | 13 +- .../exchange/bittrex/extensions-example.py | 2 +- catalyst/exchange/bundle_utils.py | 17 +- catalyst/exchange/exchange.py | 10 +- catalyst/exchange/exchange_algorithm.py | 20 +- catalyst/exchange/exchange_bundle.py | 34 +-- catalyst/exchange/exchange_errors.py | 14 +- catalyst/exchange/exchange_execution.py | 2 +- catalyst/exchange/exchange_portfolio.py | 3 +- catalyst/exchange/exchange_utils.py | 6 +- catalyst/exchange/poloniex/poloniex.py | 56 ++-- catalyst/exchange/poloniex/poloniex_api.py | 10 +- catalyst/exchange/simple_clock.py | 3 +- catalyst/finance/execution.py | 7 +- catalyst/finance/risk/period.py | 4 +- catalyst/finance/risk/risk.py | 3 +- catalyst/finance/slippage.py | 1 - catalyst/pipeline/factors/equity/__init__.py | 3 - .../pipeline/loaders/equity_pricing_loader.py | 12 +- catalyst/pipeline/loaders/events.py | 2 +- catalyst/pipeline/loaders/frame.py | 1 - catalyst/pipeline/loaders/synthetic.py | 2 +- catalyst/support/issue_44.py | 109 ------- catalyst/support/issue_47.py | 139 --------- catalyst/support/issue_55.py | 1 - catalyst/support/issue_57.py | 46 --- catalyst/support/issue_74.py | 127 -------- catalyst/support/rodrigo_1.py | 153 ---------- .../utils/calendars/exchange_calendar_open.py | 3 +- catalyst/utils/cli.py | 4 +- catalyst/utils/math_utils.py | 2 + catalyst/utils/paths.py | 2 +- catalyst/utils/run_algo.py | 12 +- tests/exchange/test_bcolz.py | 4 +- tests/exchange/test_bitfinex.py | 23 +- tests/exchange/test_bittrex.py | 42 +-- tests/exchange/test_bundle.py | 63 ++-- tests/exchange/test_ccxt.py | 21 +- tests/exchange/test_data_portal.py | 48 +-- tests/exchange/test_poloniex.py | 11 +- tests/exchange/test_server_bundle.py | 19 +- tests/exchange/test_utils.py | 3 +- 70 files changed, 757 insertions(+), 1359 deletions(-) delete mode 100644 catalyst/support/issue_44.py delete mode 100644 catalyst/support/issue_47.py delete mode 100644 catalyst/support/issue_57.py delete mode 100644 catalyst/support/issue_74.py delete mode 100644 catalyst/support/rodrigo_1.py diff --git a/catalyst/__init__.py b/catalyst/__init__.py index 508137df..7a8a26a4 100644 --- a/catalyst/__init__.py +++ b/catalyst/__init__.py @@ -29,11 +29,14 @@ from ._version import get_versions from . algorithm import TradingAlgorithm from . import api +from catalyst.utils.calendars.calendar_utils import global_calendar_dispatcher + +__version__ = get_versions()['version'] +del get_versions # PERF: Fire a warning if calendars were instantiated during catalyst import. # Having calendars doesn't break anything per-se, but it makes catalyst imports # noticeably slower, which becomes particularly noticeable in the Zipline CLI. -from catalyst.utils.calendars.calendar_utils import global_calendar_dispatcher if global_calendar_dispatcher._calendars: import warnings warnings.warn( @@ -44,10 +47,6 @@ if global_calendar_dispatcher._calendars: del global_calendar_dispatcher -__version__ = get_versions()['version'] -del get_versions - - def load_ipython_extension(ipython): from .__main__ import catalyst_magic ipython.register_magic_function(catalyst_magic, 'line_cell', 'catalyst') @@ -69,7 +68,6 @@ if os.name == 'nt': _() del _ - __all__ = [ 'TradingAlgorithm', 'api', @@ -80,7 +78,3 @@ __all__ = [ 'run_algorithm', 'utils', ] - -from ._version import get_versions -__version__ = get_versions()['version'] -del get_versions diff --git a/catalyst/__main__.py b/catalyst/__main__.py index 3a38ce23..47c7dc55 100644 --- a/catalyst/__main__.py +++ b/catalyst/__main__.py @@ -10,7 +10,6 @@ from six import text_type from catalyst.data import bundles as bundles_module from catalyst.exchange.exchange_bundle import ExchangeBundle from catalyst.exchange.exchange_utils import delete_algo_folder -from catalyst.exchange.factory import get_exchange from catalyst.utils.cli import Date, Timestamp from catalyst.utils.run_algo import _run, load_extensions @@ -520,7 +519,7 @@ def live(ctx, default=False, help='Report potential anomalies found in data bundles.' ) -def ingest_exchange(exchange_name, data_frequency, start, end, +def ingest_exchange(ctx, exchange_name, data_frequency, start, end, include_symbols, exclude_symbols, csv, show_progress, verbose, validate): """ diff --git a/catalyst/algorithm.py b/catalyst/algorithm.py index 8af375b4..1cf9476a 100644 --- a/catalyst/algorithm.py +++ b/catalyst/algorithm.py @@ -124,7 +124,6 @@ from catalyst.utils.events import ( from catalyst.utils.factory import create_simulation_parameters from catalyst.utils.math_utils import ( tolerant_equals, - round_if_near_integer, round_nearest ) from catalyst.utils.pandas_utils import clear_dataframe_indexer_caches @@ -1485,7 +1484,6 @@ class TradingAlgorithm(object): """ Converts the number of shares to the smallest tradable lot size for the asset being ordered. - """ return round_nearest(amount, asset.min_trade_size) @@ -1523,6 +1521,7 @@ class TradingAlgorithm(object): self.updated_portfolio(), self.get_datetime(), self.trading_client.current_data) + @staticmethod def __convert_order_params_for_blotter(limit_price, stop_price, style): """ diff --git a/catalyst/constants.py b/catalyst/constants.py index 6372b11f..c4111fdd 100644 --- a/catalyst/constants.py +++ b/catalyst/constants.py @@ -7,8 +7,7 @@ import logbook For example, if you want to see the DEBUG messages, run: $ export CATALYST_LOG_LEVEL=10 ''' -# LOG_LEVEL = int(os.environ.get('CATALYST_LOG_LEVEL', logbook.INFO)) -LOG_LEVEL = logbook.DEBUG +LOG_LEVEL = int(os.environ.get('CATALYST_LOG_LEVEL', logbook.INFO)) SYMBOLS_URL = 'https://s3.amazonaws.com/enigmaco/catalyst-exchanges/' \ '{exchange}/symbols.json' @@ -16,4 +15,4 @@ SYMBOLS_URL = 'https://s3.amazonaws.com/enigmaco/catalyst-exchanges/' \ DATE_TIME_FORMAT = '%Y-%m-%d %H:%M' DATE_FORMAT = '%Y-%m-%d' -AUTO_INGEST = False \ No newline at end of file +AUTO_INGEST = False diff --git a/catalyst/curate/poloniex.py b/catalyst/curate/poloniex.py index 2e51e4fb..d09afcc6 100644 --- a/catalyst/curate/poloniex.py +++ b/catalyst/curate/poloniex.py @@ -1,25 +1,33 @@ -import json, time, csv +import os +import time +import shutil +import json +import csv from datetime import datetime + import pandas as pd -import os, time, shutil, requests, logbook +import requests +import logbook + from catalyst.exchange.exchange_utils import get_exchange_symbols_filename -DT_START = int(time.mktime(datetime(2010, 1, 1, 0, 0).timetuple())) -DT_END = pd.to_datetime('today').value // 10 ** 9 -CSV_OUT_FOLDER = os.environ.get('CSV_OUT_FOLDER', '/efs/exchanges/poloniex/') -CONN_RETRIES = 2 +DT_START = int(time.mktime(datetime(2010, 1, 1, 0, 0).timetuple())) +DT_END = pd.to_datetime('today').value // 10 ** 9 +CSV_OUT_FOLDER = os.environ.get('CSV_OUT_FOLDER', '/efs/exchanges/poloniex/') +CONN_RETRIES = 2 logbook.StderrHandler().push_application() log = logbook.Logger(__name__) + class PoloniexCurator(object): ''' OHLCV data feed generator for crypto data. Based on Poloniex market data ''' - _api_path = 'https://poloniex.com/public?' - currency_pairs = [] + _api_path = 'https://poloniex.com/public?' + currency_pairs = [] def __init__(self): if not os.path.exists(CSV_OUT_FOLDER): @@ -30,10 +38,9 @@ class PoloniexCurator(object): CSV_OUT_FOLDER)) log.exception(e) - def get_currency_pairs(self): ''' - Retrieves and returns all currency pairs from the exchange + Retrieves and returns all currency pairs from the exchange ''' url = self._api_path + 'command=returnTicker' @@ -45,7 +52,7 @@ class PoloniexCurator(object): return None data = response.json() - self.currency_pairs = [] + self.currency_pairs = [] for ticker in data: self.currency_pairs.append(ticker) self.currency_pairs.sort() @@ -54,54 +61,60 @@ class PoloniexCurator(object): len(self.currency_pairs) )) - - def _retrieve_tradeID_date(self, row): ''' Helper function that reads tradeID and date fields from CSV readline ''' tId = int(row.split(',')[0]) - d = pd.to_datetime(row.split(',')[1], - infer_datetime_format=True).value // 10 ** 9 + d = pd.to_datetime(row.split(',')[1], + infer_datetime_format=True).value // 10 ** 9 return tId, d - - def retrieve_trade_history(self, currencyPair, start=DT_START, + def retrieve_trade_history(self, currencyPair, start=DT_START, end=DT_END, temp=None): ''' - Retrieves TradeHistory from exchange for a given currencyPair - between start and end dates. If no start date is provided, uses + Retrieves TradeHistory from exchange for a given currencyPair + between start and end dates. If no start date is provided, uses a system-wide one (beginning of time for cryptotrading). If no end date is provided, 'now' is used. Stores results in CSV file on disk. - - This function is called recursively to work around the + + This function is called recursively to work around the limitations imposed by the provider API. ''' csv_fn = CSV_OUT_FOLDER + 'crypto_trades-' + currencyPair + '.csv' ''' - Check what data we already have on disk, reading first and last + Check what data we already have on disk, reading first and last lines from file. Data is stored on file from NEWEST to OLDEST. ''' try: - with open(csv_fn, 'ab+') as f: + with open(csv_fn, 'ab+') as f: f.seek(0, os.SEEK_END) if(f.tell() > 2): # Check file size is not 0 - f.seek(0) # Go to start to read - last_tradeID, end_file = self._retrieve_tradeID_date(f.readline()) + f.seek(0) # Go to start to read + last_tradeID, end_file = self._retrieve_tradeID_date( + f.readline()) f.seek(-2, os.SEEK_END) # Jump to the 2nd last byte while f.read(1) != b"\n": # Until EOL is found... - f.seek(-2, os.SEEK_CUR) # ...jump back the read byte plus one more. - first_tradeID, start_file = self._retrieve_tradeID_date(f.readline()) + # ...jump back the read byte plus one more. + f.seek(-2, os.SEEK_CUR) + first_tradeID, start_file = self._retrieve_tradeID_date( + f.readline()) - if( end_file + 3600 * 6 > DT_END and ( first_tradeID == 1 - or (currencyPair == 'BTC_HUC' and first_tradeID == 2) - or (currencyPair == 'BTC_RIC' and first_tradeID == 2) - or (currencyPair == 'BTC_XCP' and first_tradeID == 2) - or (currencyPair == 'BTC_NAV' and first_tradeID == 4569) - or (currencyPair == 'BTC_POT' and first_tradeID == 23511) ) ): + if(end_file + 3600 * 6 > DT_END + and (first_tradeID == 1 + or (currencyPair == 'BTC_HUC' + and first_tradeID == 2) + or (currencyPair == 'BTC_RIC' + and first_tradeID == 2) + or (currencyPair == 'BTC_XCP' + and first_tradeID == 2) + or (currencyPair == 'BTC_NAV' + and first_tradeID == 4569) + or (currencyPair == 'BTC_POT' + and first_tradeID == 23511))): return except Exception as e: @@ -109,11 +122,11 @@ class PoloniexCurator(object): log.exception(e) ''' - Poloniex API limits querying TradeHistory to intervals smaller + Poloniex API limits querying TradeHistory to intervals smaller than 1 month, so we make sure that start date is never more than 1 month apart from end date ''' - if( end - start > 2419200 ): # 60s/min * 60min/hr * 24hr/day * 28days + if(end - start > 2419200): # 60s/min * 60min/hr * 24hr/day * 28days newstart = end - 2419200 else: newstart = start @@ -124,12 +137,11 @@ class PoloniexCurator(object): url = '{path}command=returnTradeHistory¤cyPair={pair}' \ '&start={start}&end={end}'.format( - path = self._api_path, - pair = currencyPair, - start = str(newstart), - end = str(end) + path=self._api_path, + pair=currencyPair, + start=str(newstart), + end=str(end) ) - print(url) attempts = 0 success = 0 @@ -137,14 +149,14 @@ class PoloniexCurator(object): try: response = requests.get(url) except Exception as e: - log.error('Failed to retrieve trade history data for {}'.format( - currencyPair - )) + log.error('Failed to retrieve trade history data' + 'for {}'.format(currencyPair)) log.exception(e) attempts += 1 else: try: - if isinstance(response.json(), dict) and response.json()['error']: + if(isinstance(response.json(), dict) + and response.json()['error']): log.error('Failed to to retrieve trade history data ' 'for {}: {}'.format( currencyPair, @@ -161,33 +173,32 @@ class PoloniexCurator(object): if not success: return None - ''' - If we get to transactionId == 1, and we already have that on + If we get to transactionId == 1, and we already have that on disk, we got to the end of TradeHistory for this coin. ''' - if('first_tradeID' in locals() - and response.json()[-1]['tradeID'] == first_tradeID): + if('first_tradeID' in locals() + and response.json()[-1]['tradeID'] == first_tradeID): return ''' There are primarily two scenarios: - a) There is newer data available that we need to add at - the beginning of the file. We'll retrieve all what we - need until we get to what we already have, writing it - to a temporary file; and we will write that at the + a) There is newer data available that we need to add at + the beginning of the file. We'll retrieve all what we + need until we get to what we already have, writing it + to a temporary file; and we will write that at the beginning of our existing file. - b) We are going back in time, appending at the end of - our existing TradeHistory until the first transaction + b) We are going back in time, appending at the end of + our existing TradeHistory until the first transaction for this currencyPair ''' - try: - if( 'end_file' in locals() and end_file + 3600 < end): + try: + if('end_file' in locals() and end_file + 3600 < end): if (temp is None): temp = os.tmpfile() tempcsv = csv.writer(temp) for item in response.json(): - if( item['tradeID'] <= last_tradeID ): + if(item['tradeID'] <= last_tradeID): continue tempcsv.writerow([ item['tradeID'], @@ -196,27 +207,28 @@ class PoloniexCurator(object): item['rate'], item['amount'], item['total'], - item['globalTradeID'] + item['globalTradeID'], ]) - if( response.json()[-1]['tradeID'] > last_tradeID ): - end = pd.to_datetime( response.json()[-1]['date'], - infer_datetime_format=True).value // 10 ** 9 - self.retrieve_trade_history(currencyPair, start, - end, temp=temp) + if(response.json()[-1]['tradeID'] > last_tradeID): + end = pd.to_datetime(response.json()[-1]['date'], + infer_datetime_format=True + ).value // 10**9 + self.retrieve_trade_history(currencyPair, start, + end, temp=temp) else: - with open(csv_fn,'rb+') as f: - shutil.copyfileobj(f,temp) + with open(csv_fn, 'rb+') as f: + shutil.copyfileobj(f, temp) f.seek(0) temp.seek(0) - shutil.copyfileobj(temp,f) + shutil.copyfileobj(temp, f) temp.close() end = start_file else: with open(csv_fn, 'ab') as csvfile: csvwriter = csv.writer(csvfile) for item in response.json(): - if( 'first_tradeID' in locals() - and item['tradeID'] >= first_tradeID ): + if('first_tradeID' in locals() + and item['tradeID'] >= first_tradeID): continue csvwriter.writerow([ item['tradeID'], @@ -227,70 +239,66 @@ class PoloniexCurator(object): item['total'], item['globalTradeID'] ]) - end = pd.to_datetime(response.json()[-1]['date'], - infer_datetime_format=True).value // 10 ** 9 + end = pd.to_datetime(response.json()[-1]['date'], + infer_datetime_format=True).value//10**9 except Exception as e: log.error('Error opening {}'.format(csv_fn)) log.exception(e) ''' - If we got here, we aren't done yet. Call recursively with + If we got here, we aren't done yet. Call recursively with 'end' times that go sequentially back in time. ''' self.retrieve_trade_history(currencyPair, start, end) - - def generate_ohlcv(self, df): ''' Generates OHLCV dataframe from a dataframe containing all TradeHistory by resampling with 1-minute period ''' - df.set_index('date', inplace=True) # Index by date - vol = df['total'].to_frame('volume') # set Vol aside - df.drop('total', axis=1, inplace=True) # Drop volume data - ohlc = df.resample('T').ohlc() # Resample OHLC 1min - ohlc.columns = ohlc.columns.map(lambda t: t[1]) # Raname columns by dropping 'rate' - closes = ohlc['close'].fillna(method='pad') # Pad fwd missing 'close' - ohlc = ohlc.apply(lambda x: x.fillna(closes)) # Fill N/A with last close - vol = vol.resample('T').sum().fillna(0) # Add volumes by bin - ohlcv = pd.concat([ohlc,vol], axis=1) # Concatenate OHLC + Vol + df.set_index('date', inplace=True) # Index by date + vol = df['total'].to_frame('volume') # set Vol aside + df.drop('total', axis=1, inplace=True) # Drop volume data + ohlc = df.resample('T').ohlc() # Resample OHLC 1min + ohlc.cols = ohlc.cols.map(lambda t: t[1]) # Raname cols + closes = ohlc['close'].fillna(method='pad') # Pad fwd missing close + ohlc = ohlc.apply(lambda x: x.fillna(closes)) # Fill NA w/ last close + vol = vol.resample('T').sum().fillna(0) # Add volumes by bin + ohlcv = pd.concat([ohlc, vol], axis=1) # Concat OHLC + Vol return ohlcv - - - def write_ohlcv_file(self, currencyPair): + def write_ohlcv_file(self, currencyPair): ''' Generates OHLCV data file with 1minute bars from TradeHistory on disk - ''' + ''' csv_trades = CSV_OUT_FOLDER + 'crypto_trades-' + currencyPair + '.csv' - csv_1min = CSV_OUT_FOLDER + 'crypto_1min-' + currencyPair + '.csv' - if( os.path.getmtime(csv_1min) > time.time() - 7200 ): + csv_1min = CSV_OUT_FOLDER + 'crypto_1min-' + currencyPair + '.csv' + if(os.path.getmtime(csv_1min) > time.time() - 7200): log.debug(currencyPair+': 1min data file already up to date. ' 'Delete the file if you want to rebuild it.') else: - df = pd.read_csv(csv_trades, - names=['tradeID', - 'date', - 'type', - 'rate', - 'amount', - 'total', - 'globalTradeID'], - dtype = {'tradeID': int, - 'date': str, - 'type': str, - 'rate': float, - 'amount': float, - 'total': float, - 'globalTradeID': int } - ) - df.drop(['tradeID','type','amount','globalTradeID'], + df = pd.read_csv(csv_trades, + names=['tradeID', + 'date', + 'type', + 'rate', + 'amount', + 'total', + 'globalTradeID'], + dtype={'tradeID': int, + 'date': str, + 'type': str, + 'rate': float, + 'amount': float, + 'total': float, + 'globalTradeID': int} + ) + df.drop(['tradeID', 'type', 'amount', 'globalTradeID'], axis=1, inplace=True) - df['date'] = pd.to_datetime(df['date'], infer_datetime_format=True) + df['date'] = pd.to_datetime(df['date'], infer_datetime_format=True) ohlcv = self.generate_ohlcv(df) - try: + try: with open(csv_1min, 'w') as csvfile: csvwriter = csv.writer(csvfile) for item in ohlcv.itertuples(): @@ -305,32 +313,28 @@ class PoloniexCurator(object): item.volume, ]) except Exception as e: - log.error('Error opening {}'.format(csv_fn)) + log.error('Error opening {}'.format(csv_1min)) log.exception(e) log.debug('{}: Generated 1min OHLCV data.'.format(currencyPair)) - - def onemin_to_dataframe(self, currencyPair, start, end): ''' Returns a data frame for a given currencyPair from data on disk ''' - csv_fn = CSV_OUT_FOLDER + 'crypto_1min-' + currencyPair + '.csv' - df = pd.read_csv(csv_fn, names=['date', - 'open', - 'high', - 'low', - 'close', - 'volume'] - ) - df['date'] = pd.to_datetime(df['date'],unit='s') + csv_fn = CSV_OUT_FOLDER + 'crypto_1min-' + currencyPair + '.csv' + df = pd.read_csv(csv_fn, names=['date', + 'open', + 'high', + 'low', + 'close', + 'volume']) + df['date'] = pd.to_datetime(df['date'], unit='s') df.set_index('date', inplace=True) - return df[start : end] - + return df[start:end] def generate_symbols_json(self, filename=None): ''' - Generates a symbols.json file with corresponding start_date + Generates a symbols.json file with corresponding start_date for each currencyPair ''' symbol_map = {} @@ -341,36 +345,37 @@ class PoloniexCurator(object): with open(filename, 'w') as symbols: for currencyPair in self.currency_pairs: start = None - csv_fn = '{}crypto_trades-{}.csv'.format( - CSV_OUT_FOLDER, currencyPair) - with open(csv_fn, 'r') as f: + csv_fn = '{}crypto_trades-{}.csv'.format( + CSV_OUT_FOLDER, + currencyPair) + with open(csv_fn, 'r') as f: f.seek(0, os.SEEK_END) if(f.tell() > 2): # Check file size is not 0 f.seek(-2, os.SEEK_END) # Jump to 2nd last byte while f.read(1) != b"\n": # Until EOL is found... - f.seek(-2, os.SEEK_CUR) # ...jump back the read byte plus one more. - start = pd.to_datetime( f.readline().split(',')[1], - infer_datetime_format=True) + # ...jump back the read byte plus one more. + f.seek(-2, os.SEEK_CUR) + start = pd.to_datetime(f.readline().split(',')[1], + infer_datetime_format=True) if(start is None): start = time.gmtime() base, market = currencyPair.lower().split('_') - symbol = '{market}_{base}'.format( market=market, base=base ) + symbol = '{market}_{base}'.format(market=market, base=base) symbol_map[currencyPair] = dict( - symbol = symbol, - start_date = start.strftime("%Y-%m-%d") + symbol=symbol, + start_date=start.strftime("%Y-%m-%d") ) - json.dump(symbol_map, symbols, sort_keys=True, indent=2, - separators=(',',':')) + json.dump(symbol_map, symbols, sort_keys=True, indent=2, + separators=(',', ':')) if __name__ == '__main__': pc = PoloniexCurator() pc.get_currency_pairs() - #pc.generate_symbols_json() - + # pc.generate_symbols_json() + for currencyPair in pc.currency_pairs: pc.retrieve_trade_history(currencyPair) log.debug('{} up to date.'.format(currencyPair)) pc.write_ohlcv_file(currencyPair) - diff --git a/catalyst/data/bundles/__init__.py b/catalyst/data/bundles/__init__.py index 17343b9c..b8e463dd 100644 --- a/catalyst/data/bundles/__init__.py +++ b/catalyst/data/bundles/__init__.py @@ -1,6 +1,5 @@ # These imports are necessary to force module-scope register calls to happen. from . import quandl # noqa -from . import poloniex from .core import ( UnknownBundle, bundles, diff --git a/catalyst/data/bundles/base.py b/catalyst/data/bundles/base.py index 73b5058b..c3700003 100644 --- a/catalyst/data/bundles/base.py +++ b/catalyst/data/bundles/base.py @@ -13,10 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. - from itertools import count import tarfile -from time import time, sleep +from time import sleep from abc import abstractmethod, abstractproperty import logbook @@ -37,6 +36,7 @@ log = logbook.Logger(__name__, level=LOG_LEVEL) DEFAULT_RETRIES = 5 + class BaseBundle(object): def __init__(self, asset_filter=[]): self._asset_filter = asset_filter @@ -104,11 +104,11 @@ class BaseBundle(object): def post_process_symbol_metadata(self, metadata, data): return metadata - + @abstractmethod def fetch_raw_symbol_frame(self, api_key, symbol, start_date, end_date): raise NotImplementedError() - + def ingest(self, environ, asset_db_writer, @@ -128,7 +128,7 @@ class BaseBundle(object): retries = environ.get('CATALYST_DOWNLOAD_ATTEMPTS', 5) if is_compile: - # User has instructed local compilation and ingestion of bundle. + # User has instructed local compilation & ingestion of bundle. # Fetch raw metadata for all symbols. raw_metadata = self._fetch_metadata_frame( api_key, @@ -157,9 +157,9 @@ class BaseBundle(object): show_progress=show_progress, ) - # Post-process metadata using cached symbol frames, and write to - # disk. This metadata must be written before any attempt to write - # minute data. + # Post-process metadata using cached symbol frames, and write + # to disk. This metadata must be written before any attempt + # to write minute data. metadata = self._post_process_metadata( raw_metadata, cache, @@ -184,10 +184,11 @@ class BaseBundle(object): show_progress=show_progress, ) - # For legacy purposes, this call is required to ensure the database - # contains an appropriately initialized file structure. We don't - # forsee a usecase for adjustments at this time, but may later - # choose to expose this functionality in the future. + # For legacy purposes, this call is required to ensure the + # database contains an appropriately initialized file + # structure. We don't forsee a usecase for adjustments at + # this time, but may later choose to expose this functionality + # in the future. adjustment_writer.write( splits=( pd.concat(self.splits, ignore_index=True) @@ -232,12 +233,12 @@ class BaseBundle(object): tar.extractall(output_dir) def _fetch_metadata_frame(self, - api_key, - cache, - retries=DEFAULT_RETRIES, - environ=None, - show_progress=False): - + api_key, + cache, + retries=DEFAULT_RETRIES, + environ=None, + show_progress=False): + # Setup raw metadata iterator to fetch pages if necessary. raw_iter = self._fetch_metadata_iter(api_key, cache, retries, environ) @@ -251,7 +252,7 @@ class BaseBundle(object): show_percent=False, ) as blocks: metadata = pd.concat(blocks, ignore_index=True) - + return metadata def _fetch_metadata_iter(self, api_key, cache, retries, environ): @@ -269,21 +270,20 @@ class BaseBundle(object): page_number, ) break - except ValueError as e: + except ValueError: raw = pd.DataFrame([]) break - except Exception as e: + except Exception: log.exception( 'Failed to load metadata from {}. ' 'Retrying.'.format(self.name) - ) + ) else: raise ValueError( 'Failed to download metadata page {} after {} ' 'attempts.'.format(page_number, retries) ) - if raw.empty: # Empty DataFrame signals completion. break @@ -305,7 +305,7 @@ class BaseBundle(object): columns=self.md_column_names, index=metadata.index, ) - + # Iterate over the available symbols, loading the asset's raw symbol # data from the cache. The final metadata is computed and recorded in # the appropriate row depending on the asset's id. @@ -318,22 +318,22 @@ class BaseBundle(object): show_percent=False, ) as symbols_map: for asset_id, symbol in symbols_map: - # Attempt to load data from disk, the cache should have an entry - # for each symbol at this point of the execution. If one does - # not exist, we should fail. + # Attempt to load data from disk, the cache should have an + # entry for each symbol at this point of the execution. If one + # does not exist, we should fail. key = '{sym}.daily.frame'.format(sym=symbol) try: raw_data = cache[key] except KeyError: raise ValueError( - 'Unable to find cached data for symbol: {0}'.format(symbol) - ) + 'Unable to find cached data for symbol:' + ' {0}'.format(symbol)) # Perform and require post-processing of metadata. final_symbol_metadata = self.post_process_symbol_metadata( asset_id, metadata.iloc[asset_id], - raw_data, + raw_data, ) # Record symbol's final metadata. @@ -363,8 +363,8 @@ class BaseBundle(object): # returns the cached data unaltered. The `should_sleep` flag # indicates that an API call was attempted, and that we should be # ensure aren't exceeding our rate limit before proceeding to the - # next symbol. If the raw_data is updated, it is cached before being - # returned. + # next symbol. If the raw_data is updated, it is cached before + # being returned. raw_data, should_sleep = self._maybe_update_symbol_frame( start_time, api_key, @@ -414,7 +414,7 @@ class BaseBundle(object): last = start_session if raw_data is not None and len(raw_data) > 0: last = raw_data.index[-1].tz_localize('UTC') - + should_sleep = False # Determine time at which cached data will be considered stale. @@ -455,7 +455,7 @@ class BaseBundle(object): retries=DEFAULT_RETRIES): # Data for symbol is old enough to attempt an update or is not - # present in the cache. Fetch raw data for a single symbol + # present in the cache. Fetch raw data for a single symbol # with requested intervals and frequency. Retry as necessary. for _ in range(retries): try: @@ -468,7 +468,6 @@ class BaseBundle(object): data_frequency, ) raw_data.index = pd.to_datetime(raw_data.index, utc=True) - #raw_data.index = raw_data.index.tz_localize('UTC') # Filter incoming data to fit start and end sessions. raw_data = raw_data[ @@ -482,7 +481,7 @@ class BaseBundle(object): return raw_data - except Exception as e: + except Exception: log.exception( 'Exception raised fetching {name} data. Retrying.' .format(name=self.name) diff --git a/catalyst/data/bundles/base_pricing.py b/catalyst/data/bundles/base_pricing.py index 7b94e4bc..ecb9b778 100644 --- a/catalyst/data/bundles/base_pricing.py +++ b/catalyst/data/bundles/base_pricing.py @@ -16,6 +16,7 @@ from catalyst.data.bundles.base import BaseBundle from catalyst.utils.memoize import lazyval + class BasePricingBundle(BaseBundle): @lazyval def md_dtypes(self): @@ -38,6 +39,7 @@ class BasePricingBundle(BaseBundle): ('volume', 'float64'), ] + class BaseCryptoPricingBundle(BasePricingBundle): @lazyval def calendar_name(self): @@ -55,6 +57,7 @@ class BaseCryptoPricingBundle(BasePricingBundle): def dividends(self): return [] + class BaseEquityPricingBundle(BasePricingBundle): @lazyval def calendar_name(self): diff --git a/catalyst/data/bundles/core.py b/catalyst/data/bundles/core.py index a25591de..58160ab1 100644 --- a/catalyst/data/bundles/core.py +++ b/catalyst/data/bundles/core.py @@ -37,6 +37,7 @@ from catalyst.utils.cli import maybe_show_progress ONE_MEGABYTE = 1024 * 1024 + def asset_db_path(bundle_name, timestr, environ=None, db_version=None): return pth.data_path( asset_db_relative(bundle_name, timestr, environ, db_version), @@ -135,6 +136,7 @@ def ingestions_for_bundle(bundle, environ=None): reverse=True, ) + def download_with_progress(url, chunk_size, **progress_kwargs): """ Download streaming data from a URL, printing progress information to the @@ -705,4 +707,5 @@ def _make_bundle_core(): ) -bundles, register_bundle, register, unregister, ingest, load, clean = _make_bundle_core() +bundles, register_bundle, register, unregister, ingest, load, clean = \ + _make_bundle_core() diff --git a/catalyst/data/bundles/poloniex.py b/catalyst/data/bundles/poloniex.py index 64cc2d27..6428dbb0 100644 --- a/catalyst/data/bundles/poloniex.py +++ b/catalyst/data/bundles/poloniex.py @@ -14,19 +14,17 @@ # limitations under the License. import sys - -from datetime import datetime +from six.moves.urllib.parse import urlencode import pandas as pd -from six.moves.urllib.parse import urlencode - from catalyst.data.bundles.core import register_bundle from catalyst.data.bundles.base_pricing import BaseCryptoPricingBundle from catalyst.utils.memoize import lazyval from catalyst.curate.poloniex import PoloniexCurator + class PoloniexBundle(BaseCryptoPricingBundle): @lazyval def name(self): @@ -46,7 +44,8 @@ class PoloniexBundle(BaseCryptoPricingBundle): @lazyval def tar_url(self): return ( - 'https://s3.amazonaws.com/enigmaco/catalyst-bundles/poloniex/poloniex-bundle.tar.gz' + 'https://s3.amazonaws.com/enigmaco/catalyst-bundles/' + 'poloniex/poloniex-bundle.tar.gz' ) @lazyval @@ -67,12 +66,11 @@ class PoloniexBundle(BaseCryptoPricingBundle): raw = raw.sort_index().reset_index() raw.rename( - columns={'index':'symbol'}, + columns={'index': 'symbol'}, inplace=True, ) raw = raw[raw['isFrozen'] == 0] - return raw def post_process_symbol_metadata(self, asset_id, sym_md, sym_data): @@ -98,7 +96,8 @@ class PoloniexBundle(BaseCryptoPricingBundle): frequency): # TODO: replace this with direct exchange call - # The end date and frequency should be used to calculate the number of bars + # The end date and frequency should be used to + # calculate the number of bars if(frequency == 'minute'): pc = PoloniexCurator() raw = pc.onemin_to_dataframe(symbol, start_date, end_date) @@ -116,8 +115,9 @@ class PoloniexBundle(BaseCryptoPricingBundle): ) raw.set_index('date', inplace=True) - # BcolzDailyBarReader introduces a 1/1000 factor in the way pricing is stored - # on disk, which we compensate here to get the right pricing amounts + # BcolzDailyBarReader introduces a 1/1000 factor in the way + # pricing is stored on disk, which we compensate here to get + # the right pricing amounts # ref: data/us_equity_pricing.py scale = 1 raw.loc[:, 'open'] /= scale @@ -139,7 +139,6 @@ class PoloniexBundle(BaseCryptoPricingBundle): return self._format_polo_query(query_params) - def _format_data_url(self, api_key, symbol, @@ -162,27 +161,26 @@ class PoloniexBundle(BaseCryptoPricingBundle): ('end', end_date.value / 10**9), ('period', period), ] - + return self._format_polo_query(query_params) - + def _format_polo_query(self, query_params): # TODO: got against the exchange object return 'https://poloniex.com/public?{query}'.format( query=urlencode(query_params), ) -''' -As a second parameter, you can pass an array of currency pairs -that will be processed as an asset_filter to only process that + +''' +As a second parameter, you can pass an array of currency pairs +that will be processed as an asset_filter to only process that subset of assets in the bundle, such as: register_bundle(PoloniexBundle, ['USDT_BTC',]) For a production environment make sure to use (to bundle all pairs): register_bundle(PoloniexBundle) ''' - if 'ingest' in sys.argv and '-c' in sys.argv: register_bundle(PoloniexBundle) else: register_bundle(PoloniexBundle, create_writers=False) - diff --git a/catalyst/data/bundles/quandl.py b/catalyst/data/bundles/quandl.py index 1255449c..79e3eba8 100644 --- a/catalyst/data/bundles/quandl.py +++ b/catalyst/data/bundles/quandl.py @@ -16,7 +16,6 @@ from datetime import datetime import pandas as pd - from six.moves.urllib.parse import urlencode from catalyst.data.bundles.core import register_bundle @@ -26,25 +25,16 @@ from catalyst.utils.memoize import lazyval """ Module for building a complete daily dataset from Quandl's WIKI dataset. """ -from itertools import count -import tarfile -from time import time, sleep -from datetime import datetime - from logbook import Logger -import pandas as pd -from six.moves.urllib.parse import urlencode - -from catalyst.utils.calendars import register_calendar_alias -from catalyst.utils.cli import maybe_show_progress - -from . import core as bundles from catalyst.constants import LOG_LEVEL +from catalyst.utils.calendars import register_calendar_alias + log = Logger(__name__, level=LOG_LEVEL) seconds_per_call = (pd.Timedelta('10 minutes') / 2000).total_seconds() + class QuandlBundle(BaseEquityPricingBundle): @lazyval def name(self): @@ -109,8 +99,8 @@ class QuandlBundle(BaseEquityPricingBundle): # Filter out invalid symbols raw = raw[~raw.symbol.isin(self._excluded_symbols)] - # cut out all the other stuff in the name column - # we need to escape the paren because it is actually splitting on a regex + # cut out all the other stuff in the name column. We need to + # escape the paren because it is actually splitting on a regex raw.asset_name = raw.asset_name.str.split(r' \(', 1).str.get(0) return raw @@ -175,7 +165,6 @@ class QuandlBundle(BaseEquityPricingBundle): df['sid'] = asset_id self.splits.append(df) - def _update_dividends(self, asset_id, raw_data): divs = raw_data.ex_dividend df = pd.DataFrame({'amount': divs[divs != 0]}) @@ -186,7 +175,6 @@ class QuandlBundle(BaseEquityPricingBundle): df['record_date'] = df['declared_date'] = df['pay_date'] = pd.NaT self.dividends.append(df) - def _format_metadata_url(self, api_key, page_number): """Build the query RL for the quandl WIKI metadata. """ @@ -200,10 +188,10 @@ class QuandlBundle(BaseEquityPricingBundle): query_params = [('api_key', api_key)] + query_params return ( - 'https://www.quandl.com/api/v3/datasets.csv?' + urlencode(query_params) + 'https://www.quandl.com/api/v3/datasets.csv?' + + urlencode(query_params) ) - def _format_wiki_url(self, api_key, symbol, @@ -229,5 +217,6 @@ class QuandlBundle(BaseEquityPricingBundle): ) ) + register_calendar_alias('QUANDL', 'NYSE') register_bundle(QuandlBundle) diff --git a/catalyst/data/data_portal.py b/catalyst/data/data_portal.py index 1f7ec6b3..34c720d0 100644 --- a/catalyst/data/data_portal.py +++ b/catalyst/data/data_portal.py @@ -656,11 +656,11 @@ class DataPortal(object): return spot_value def _get_minutely_spot_value(self, - asset, - column, - dt, - data_frequency, - ffill=False): + asset, + column, + dt, + data_frequency, + ffill=False): reader = self._get_pricing_reader(data_frequency) @@ -706,7 +706,7 @@ class DataPortal(object): asset, column, dt, - ffill, + ffill, 'minute', ) diff --git a/catalyst/data/dispatch_bar_reader.py b/catalyst/data/dispatch_bar_reader.py index 7dfd7e95..a8e7429b 100644 --- a/catalyst/data/dispatch_bar_reader.py +++ b/catalyst/data/dispatch_bar_reader.py @@ -133,11 +133,13 @@ class AssetDispatchBarReader(with_metaclass(ABCMeta)): return results + class AssetDispatchMinuteBarReader(AssetDispatchBarReader): def _dt_window_size(self, start_dt, end_dt): return len(self.trading_calendar.minutes_in_range(start_dt, end_dt)) + class AssetDispatchSessionBarReader(AssetDispatchBarReader): def _dt_window_size(self, start_dt, end_dt): diff --git a/catalyst/data/loader.py b/catalyst/data/loader.py index e58127f7..b9227b3e 100644 --- a/catalyst/data/loader.py +++ b/catalyst/data/loader.py @@ -12,7 +12,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import datetime import os from collections import OrderedDict @@ -129,11 +128,13 @@ def load_crypto_market_data(trading_day=None, trading_days=None, # before this date. ''' if(bundle_data): - # If we are using the bundle to retrieve the cryptobenchmark, find the last - # date for which there is trading data in the bundle - asset = bundle_data.asset_finder.lookup_symbol(symbol=bm_symbol,as_of_date=None) + # If we are using the bundle to retrieve the cryptobenchmark, find + # the last date for which there is trading data in the bundle + asset = bundle_data.asset_finder.lookup_symbol( + symbol=bm_symbol,as_of_date=None) ix = bundle_data.daily_bar_reader._last_rows[asset.sid] - last_date = pd.to_datetime(bundle_data.daily_bar_reader._spot_col('day')[ix],unit='s') + last_date = pd.to_datetime( + bundle_data.daily_bar_reader._spot_col('day')[ix],unit='s') else: last_date = trading_days[trading_days.get_loc(now, method='ffill') - 2] ''' @@ -164,8 +165,8 @@ def load_crypto_market_data(trading_day=None, trading_days=None, br.loc[start_dt] = 0 br = br.sort_index() - # Override first_date for treasury data since we have it for many more years - # and is independent of crypto data + # Override first_date for treasury data since we have it for many more + # years and is independent of crypto data first_date_treasury = pd.Timestamp('1990-01-02', tz='UTC') tc = ensure_treasury_data( bm_symbol, @@ -301,14 +302,14 @@ def ensure_crypto_benchmark_data(symbol, if (bundle == 'poloniex'): ''' - If we're using the Poloniex bundle, we'll get the benchmark from the bundle - instead of downloading it from Poloniex every time we need it. - Poloniex has a captcha for API queries originating from outside the US that - prevents users abroad from getting Catalyst to work + If we're using the Poloniex bundle, we'll get the benchmark from the + bundle instead of downloading it from Poloniex every time we need it. + Poloniex has a captcha for API queries originating from outside the US + that prevents users abroad from getting Catalyst to work ''' logger.info( - ( - 'Retrieving benchmark data from bundle for {symbol!r} from {first_date} to {last_date}'), + ('Retrieving benchmark data from bundle for {symbol!r}' + ' from {first_date} to {last_date}'), symbol=symbol, first_date=first_date, last_date=last_date) asset = bundle_data.asset_finder.lookup_symbol(symbol=symbol, @@ -330,11 +331,12 @@ def ensure_crypto_benchmark_data(symbol, last_date)] else: - # This is how it used to be: downloading the benchmark everytime. - # Leaving this code here to be repurposed in the future for other bundles. + # This is how it used to be: downloading the benchmark everytime. + # Leaving this code here to be repurposed in the future for + # other bundles. logger.info( - ( - 'Downloading benchmark data for {symbol!r} from {first_date} to {last_date}'), + ('Downloading benchmark data for {symbol!r}' + ' from {first_date} to {last_date}'), symbol=symbol, first_date=first_date, last_date=last_date) raise DeprecationWarning('poloniex bundle deprecated') @@ -431,67 +433,6 @@ def ensure_benchmark_data(symbol, first_date, last_date, now, trading_day, return data -def ensure_benchmark_data(symbol, first_date, last_date, now, trading_day, - environ=None): - """ - Ensure we have benchmark data for `symbol` from `first_date` to `last_date` - - Parameters - ---------- - symbol : str - The symbol for the benchmark to load. - first_date : pd.Timestamp - First required date for the cache. - last_date : pd.Timestamp - Last required date for the cache. - now : pd.Timestamp - The current time. This is used to prevent repeated attempts to - re-download data that isn't available due to scheduling quirks or other - failures. - trading_day : pd.CustomBusinessDay - A trading day delta. Used to find the day before first_date so we can - get the close of the day prior to first_date. - - We attempt to download data unless we already have data stored at the data - cache for `symbol` whose first entry is before or on `first_date` and whose - last entry is on or after `last_date`. - - If we perform a download and the cache criteria are not satisfied, we wait - at least one hour before attempting a redownload. This is determined by - comparing the current time to the result of os.path.getmtime on the cache - path. - """ - filename = get_benchmark_filename(symbol) - data = _load_cached_data(filename, first_date, last_date, now, 'benchmark', - environ) - if data is not None: - return data - - # If no cached data was found or it was missing any dates then download the - # necessary data. - logger.info( - ('Downloading benchmark data for {symbol!r} ' - 'from {first_date} to {last_date}'), - symbol=symbol, - first_date=first_date - trading_day, - last_date=last_date - ) - - try: - data = get_benchmark_returns( - symbol, - first_date - trading_day, - last_date, - ) - data.to_csv(get_data_filepath(filename, environ)) - except (OSError, IOError, HTTPError): - logger.exception('Failed to cache the new benchmark returns') - raise - if not has_data_for_dates(data, first_date, last_date): - logger.warn("Still don't have expected data after redownload!") - return data - - def ensure_treasury_data(symbol, first_date, last_date, now, environ=None): """ Ensure we have treasury data from treasury module associated with diff --git a/catalyst/data/minute_bars.py b/catalyst/data/minute_bars.py index b2491a5a..3ef13cc2 100644 --- a/catalyst/data/minute_bars.py +++ b/catalyst/data/minute_bars.py @@ -341,12 +341,10 @@ class BcolzMinuteBarMetadata(object): 'end_session': str(self.end_session.date()), # Write these values for backwards compatibility 'first_trading_day': str(self.start_session.date()), - 'market_opens': ( - market_opens.values.astype('datetime64[m]'). - astype(np.int64).tolist()), - 'market_closes': ( - market_closes.values.astype('datetime64[m]'). - astype(np.int64).tolist()), + 'market_opens': (market_opens.values.astype('datetime64[m]'). + astype(np.int64).tolist()), + 'market_closes': (market_closes.values.astype('datetime64[m]'). + astype(np.int64).tolist()), } with open(self.metadata_path(rootdir), 'w+') as fp: json.dump(metadata, fp) @@ -1256,8 +1254,8 @@ class BcolzMinuteBarReader(MinuteBarReader): values = carray[start_idx:end_idx + 1] if indices_to_exclude is not None: for excl_start, excl_stop in indices_to_exclude[::-1]: - excl_slice = np.s_[ - excl_start - start_idx:excl_stop - start_idx + 1] + excl_slice = np.s_[excl_start - start_idx:excl_stop + - start_idx + 1] values = np.delete(values, excl_slice) where = values != 0 @@ -1320,9 +1318,8 @@ class H5MinuteBarUpdateWriter(object): def __init__(self, path, complevel=None, complib=None): self._complevel = complevel if complevel \ - is not None else self._COMPLEVEL - self._complib = complib if complib \ - is not None else self._COMPLIB + is not None else self._COMPLEVEL + self._complib = complib if complib is not None else self._COMPLIB self._path = path def write(self, frames): diff --git a/catalyst/data/us_equity_pricing.py b/catalyst/data/us_equity_pricing.py index 8e74ab43..8bb8427b 100644 --- a/catalyst/data/us_equity_pricing.py +++ b/catalyst/data/us_equity_pricing.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import division # Python2 req to have division of ints yield float +from __future__ import division # Python2 req for division of ints yield float from errno import ENOENT from functools import partial @@ -120,7 +120,8 @@ SQLITE_STOCK_DIVIDEND_PAYOUT_COLUMN_DTYPES = { UINT32_MAX = iinfo(uint32).max UINT64_MAX = iinfo(uint64).max -PRICE_ADJUSTMENT_FACTOR = 1000000000 # Provides 9 decimals resolution. Also affects _equities.pyx L220 +# Provides 9 decimals resolution. Also affects _equities.pyx L220 +PRICE_ADJUSTMENT_FACTOR = 1000000000 def check_uint32_safe(value, colname): @@ -130,6 +131,7 @@ def check_uint32_safe(value, colname): "for uint32" % (value, colname) ) + def check_uint64_safe(value, colname): if value >= UINT64_MAX: raise ValueError( @@ -322,8 +324,8 @@ class BcolzDailyBarWriter(object): # Maps column name -> output carray. columns = { k: carray(array([], dtype=uint64)) - if k in OHLCV - else carray(array([], dtype=uint32)) + if k in OHLCV + else carray(array([], dtype=uint32)) for k in US_EQUITY_PRICING_BCOLZ_COLUMNS } @@ -439,11 +441,13 @@ class BcolzDailyBarWriter(object): return raw_data winsorise_uint64(raw_data, invalid_data_behavior, 'volume', *OHLC) - processed = (raw_data[list(OHLC)] * PRICE_ADJUSTMENT_FACTOR).astype('uint64') + processed = (raw_data[list(OHLC)] + * PRICE_ADJUSTMENT_FACTOR).astype('uint64') dates = raw_data.index.values.astype('datetime64[s]') check_uint32_safe(dates.max().view(np.int64), 'day') processed['day'] = dates.astype('uint32') - processed['volume'] = (raw_data.volume * PRICE_ADJUSTMENT_FACTOR).astype('uint64') + processed['volume'] = (raw_data.volume + * PRICE_ADJUSTMENT_FACTOR).astype('uint64') return ctable.fromdataframe(processed) @@ -496,7 +500,7 @@ class BcolzDailyBarReader(SessionBarReader): The data in these columns is interpreted as follows: - - Price columns ('open', 'high', 'low', 'close') and Volume are interpreted + - Price columns ('open', 'high', 'low', 'close') and Volume are interpreted as 10^9 * as-traded dollar value. - Day is interpreted as seconds since midnight UTC, Jan 1, 1970. - Id is the asset id of the row. diff --git a/catalyst/examples/buy_and_hodl.py b/catalyst/examples/buy_and_hodl.py index 74b04238..6722665f 100644 --- a/catalyst/examples/buy_and_hodl.py +++ b/catalyst/examples/buy_and_hodl.py @@ -19,7 +19,7 @@ import matplotlib.pyplot as plt from catalyst import run_algorithm from catalyst.api import (order_target_value, symbol, record, - cancel_order, get_open_orders, ) + cancel_order, get_open_orders, ) def initialize(context): diff --git a/catalyst/examples/buy_btc_simple.py b/catalyst/examples/buy_btc_simple.py index 5e4ebd57..52463d58 100644 --- a/catalyst/examples/buy_btc_simple.py +++ b/catalyst/examples/buy_btc_simple.py @@ -1,30 +1,34 @@ ''' - This is a very simple example referenced in the beginner's tutorial: - https://enigmampc.github.io/catalyst/beginner-tutorial.html + This is a very simple example referenced in the beginner's tutorial: + https://enigmampc.github.io/catalyst/beginner-tutorial.html - Run this example, by executing the following from your terminal: - catalyst ingest-exchange -x bitfinex -f daily -i btc_usdt - catalyst run -f buy_btc_simple.py -x bitfinex --start 2016-1-1 --end 2017-9-30 -o buy_btc_simple_out.pickle + Run this example, by executing the following from your terminal: + catalyst ingest-exchange -x bitfinex -f daily -i btc_usdt + catalyst run -f buy_btc_simple.py -x bitfinex --start 2016-1-1 \ + --end 2017-9-30 -o buy_btc_simple_out.pickle - If you want to run this code using another exchange, make sure that - the asset is available on that exchange. For example, if you were to run - it for exchange Poloniex, you would need to edit the following line: + If you want to run this code using another exchange, make sure that + the asset is available on that exchange. For example, if you were to run + it for exchange Poloniex, you would need to edit the following line: - context.asset = symbol('btc_usdt') # note 'usdt' instead of 'usd' + context.asset = symbol('btc_usdt') # note 'usdt' instead of 'usd' - and specify exchange poloniex as follows: - catalyst ingest-exchange -x poloniex -f daily -i btc_usdt - catalyst run -f buy_btc_simple.py -x poloniex --start 2016-1-1 --end 2017-9-30 -o buy_btc_simple_out.pickle + and specify exchange poloniex as follows: + catalyst ingest-exchange -x poloniex -f daily -i btc_usdt + catalyst run -f buy_btc_simple.py -x poloniex --start 2016-1-1 \ + --end 2017-9-30 -o buy_btc_simple_out.pickle - To see which assets are available on each exchange, visit: - https://www.enigma.co/catalyst/status + To see which assets are available on each exchange, visit: + https://www.enigma.co/catalyst/status ''' from catalyst.api import order, record, symbol + def initialize(context): context.asset = symbol('btc_usd') + def handle_data(context, data): order(context.asset, 1) - record(btc = data.current(context.asset, 'price')) \ No newline at end of file + record(btc=data.current(context.asset, 'price')) diff --git a/catalyst/examples/buy_low_sell_high.py b/catalyst/examples/buy_low_sell_high.py index acf481e0..b3afb723 100644 --- a/catalyst/examples/buy_low_sell_high.py +++ b/catalyst/examples/buy_low_sell_high.py @@ -1,12 +1,13 @@ ''' -This algorithm requires an additional library (ta-lib) beyond those required by catalyst. -Install it first by running: +This algorithm requires an additional library (ta-lib) beyond those +required by catalyst. Install it first by running: $ pip install TA-Lib -If you get build errors like "fatal error: ta-lib/ta_libc.h: No such file or directory" -it typically means that it can't find the underlying TA-Lib library and needs to be installed. -See https://mrjbq7.github.io/ta-lib/install.html for instructions on how to install -the required dependencies. +If you get build errors like: + "fatal error: ta-lib/ta_libc.h: No such file or directory" +it typically means that it can't find the underlying TA-Lib library and it +needs to be installed. See https://mrjbq7.github.io/ta-lib/install.html for +instructions on how to install the required dependencies. ''' import talib @@ -100,8 +101,8 @@ def _handle_data(context, data): if price < cost_basis: is_buy = True - elif position.amount > 0 and \ - price > cost_basis * (1 + context.PROFIT_TARGET): + elif(position.amount > 0 + and price > cost_basis * (1 + context.PROFIT_TARGET)): profit = (price * position.amount) - (cost_basis * position.amount) log.info('closing position, taking profit: {}'.format(profit)) order_target_percent( diff --git a/catalyst/examples/buy_low_sell_high_live.py b/catalyst/examples/buy_low_sell_high_live.py index a1fd326c..422033a6 100644 --- a/catalyst/examples/buy_low_sell_high_live.py +++ b/catalyst/examples/buy_low_sell_high_live.py @@ -88,8 +88,8 @@ def _handle_data(context, data): if price < cost_basis: is_buy = True - elif position.amount > 0 and \ - price > cost_basis * (1 + context.PROFIT_TARGET): + elif(position.amount > 0 + and price > cost_basis * (1 + context.PROFIT_TARGET)): profit = (price * position.amount) - (cost_basis * position.amount) log.info('closing position, taking profit: {}'.format(profit)) order_target_percent( diff --git a/catalyst/examples/dual_moving_average.py b/catalyst/examples/dual_moving_average.py index a73461f1..f54d91b6 100644 --- a/catalyst/examples/dual_moving_average.py +++ b/catalyst/examples/dual_moving_average.py @@ -4,13 +4,14 @@ from logbook import Logger import matplotlib.pyplot as plt from catalyst import run_algorithm -from catalyst.api import (order, record, symbol, order_target_percent, - get_open_orders) +from catalyst.api import (record, symbol, order_target_percent, + get_open_orders) from catalyst.exchange.stats_utils import extract_transactions NAMESPACE = 'dual_moving_average' log = Logger(NAMESPACE) + def initialize(context): context.i = 0 context.asset = symbol('ltc_usd') @@ -25,16 +26,22 @@ def handle_data(context, data): # Skip as many bars as long_window to properly compute the average context.i += 1 if context.i < long_window: - return + return # Compute moving averages calling data.history() for each # moving average with the appropriate parameters. We choose to use # minute bars for this simulation -> freq="1m" # Returns a pandas dataframe. - short_mavg = data.history(context.asset, 'price', - bar_count=short_window, frequency="1m").mean() - long_mavg = data.history(context.asset, 'price', - bar_count=long_window, frequency="1m").mean() + short_mavg = data.history(context.asset, + 'price', + bar_count=short_window, + frequency="1m", + ).mean() + long_mavg = data.history(context.asset, + 'price', + bar_count=long_window, + frequency="1m", + ).mean() # Let's keep the price of our asset in a more handy variable price = data.current(context.asset, 'price') @@ -67,11 +74,11 @@ def handle_data(context, data): # Trading logic if short_mavg > long_mavg and pos_amount == 0: - # we buy 100% of our portfolio for this asset - order_target_percent(context.asset, 1) + # we buy 100% of our portfolio for this asset + order_target_percent(context.asset, 1) elif short_mavg < long_mavg and pos_amount > 0: - # we sell all our positions for this asset - order_target_percent(context.asset, 0) + # we sell all our positions for this asset + order_target_percent(context.asset, 0) def analyze(context, perf): @@ -89,11 +96,13 @@ def analyze(context, perf): # Second chart: Plot asset price, moving averages and buys/sells ax2 = plt.subplot(412, sharex=ax1) - perf.loc[:, ['price','short_mavg','long_mavg']].plot(ax=ax2, label='Price') + perf.loc[:, ['price', 'short_mavg', 'long_mavg']].plot( + ax=ax2, + label='Price') ax2.legend_.remove() ax2.set_ylabel('{asset}\n({base})'.format( - asset = context.asset.symbol, - base = base_currency + asset=context.asset.symbol, + base=base_currency )) start, end = ax2.get_ylim() ax2.yaxis.set_ticks(np.arange(start, end, (end-start)/5)) @@ -150,4 +159,4 @@ if __name__ == '__main__': base_currency='usd', start=pd.to_datetime('2017-9-22', utc=True), end=pd.to_datetime('2017-9-23', utc=True), - ) \ No newline at end of file + ) diff --git a/catalyst/examples/dual_vwap.py b/catalyst/examples/dual_vwap.py index 52c1789d..7059b865 100644 --- a/catalyst/examples/dual_vwap.py +++ b/catalyst/examples/dual_vwap.py @@ -52,13 +52,14 @@ def initialize(context): schedule_function( rebalance, - time_rules=times_rules.every_minute(), + time_rules=date_rules.every_minute(), ) def before_trading_start(context, data): context.pipeline_data = pipeline_output('vwap_pipeline') + def make_pipeline(context): return Pipeline( columns={ @@ -69,6 +70,7 @@ def make_pipeline(context): } ) + def rebalance(context, data): context.i += 1 @@ -111,7 +113,6 @@ def rebalance(context, data): long_mavg=long_mavg, volume=volume, ) - def analyze(context=None, results=None): @@ -124,10 +125,11 @@ def analyze(context=None, results=None): ax2 = plt.subplot(612, sharex=ax1) ax2.set_ylabel('{asset} (USD)'.format(asset=context.ASSET_NAME)) - (context.TICK_SIZE*results[['price', 'short_mavg', 'long_mavg']]).plot(ax=ax2) + (context.TICK_SIZE*results[['price', + 'short_mavg', + 'long_mavg']]).plot(ax=ax2) trans = results.ix[[t != [] for t in results.transactions]] - amounts = [t[0]['amount'] for t in trans.transactions] buys = trans.ix[ [t[0]['amount'] > 0 for t in trans.transactions] diff --git a/catalyst/examples/mean_reversion_simple.py b/catalyst/examples/mean_reversion_simple.py index 450a73f7..4508c8e5 100644 --- a/catalyst/examples/mean_reversion_simple.py +++ b/catalyst/examples/mean_reversion_simple.py @@ -1,4 +1,4 @@ -# For this example, we're going to write a simple momentum script. When the +# For this example, we're going to write a simple momentum script. When the # stock goes up quickly, we're going to buy; when it goes down quickly, we're # going to sell. Hopefully we'll ride the waves. import os @@ -12,8 +12,8 @@ from logbook import Logger from catalyst import run_algorithm from catalyst.api import symbol, record, order_target_percent, get_open_orders -from catalyst.exchange.stats_utils import extract_transactions, \ - get_pretty_stats +from catalyst.exchange.stats_utils import extract_transactions + # We give a name to the algorithm which Catalyst will use to persist its state. # In this example, Catalyst will create the `.catalyst/data/live_algos` # directory. If we stop and start the algorithm, Catalyst will resume its @@ -122,7 +122,7 @@ def handle_data(context, data): # Another powerful built-in feature of the Catalyst backtester is the # portfolio object. The portfolio object tracks your positions, cash, # cost basis of specific holdings, and more. In this line, we calculate - # how long or short our position is at this minute. + # how long or short our position is at this minute. pos_amount = context.portfolio.positions[context.market].amount if rsi[-1] <= context.RSI_OVERSOLD and pos_amount == 0: @@ -250,7 +250,9 @@ if __name__ == '__main__': timestr = time.strftime('%Y%m%d-%H%M%S') out = os.path.join(folder, '{}.p'.format(timestr)) - # catalyst run -f catalyst/examples/mean_reversion_simple.py -x bitfinex -s 2017-10-1 -e 2017-11-10 -c usdt -n mean-reversion --data-frequency minute --capital-base 10000 + # catalyst run -f catalyst/examples/mean_reversion_simple.py \ + # -x bitfinex -s 2017-10-1 -e 2017-11-10 -c usdt -n mean-reversion \ + # --data-frequency minute --capital-base 10000 run_algorithm( capital_base=0.1, data_frequency='minute', diff --git a/catalyst/examples/portfolio_optimization.py b/catalyst/examples/portfolio_optimization.py index bb1660a2..e93b2daf 100644 --- a/catalyst/examples/portfolio_optimization.py +++ b/catalyst/examples/portfolio_optimization.py @@ -1,7 +1,7 @@ -'''Use this code to execute a portfolio optimization model. This code - will select the portfolio with the maximum Sharpe Ratio. The parameters +'''Use this code to execute a portfolio optimization model. This code + will select the portfolio with the maximum Sharpe Ratio. The parameters are set to use 180 days of historical data and rebalance every 30 days. - + This is the code used in the following article: https://blog.enigma.co/markowitz-portfolio-optimization-for-cryptocurrencies-in-catalyst-b23c38652556 @@ -15,115 +15,130 @@ import os import pytz import numpy as np import pandas as pd -from scipy.optimize import minimize import matplotlib.pyplot as plt from datetime import datetime -from catalyst.api import record, symbol, symbols, order_target_percent +from catalyst.api import record, symbols, order_target_percent from catalyst.utils.run_algo import run_algorithm np.set_printoptions(threshold='nan', suppress=True) def initialize(context): - # Portfolio assets list - context.assets = symbols('btc_usdt', 'eth_usdt', 'ltc_usdt', 'dash_usdt', - 'xmr_usdt') - context.nassets = len(context.assets) - # Set the time window that will be used to compute expected return - # and asset correlations - context.window = 180 - # Set the number of days between each portfolio rebalancing - context.rebalance_period = 30 - context.i = 0 + # Portfolio assets list + context.assets = symbols('btc_usdt', 'eth_usdt', 'ltc_usdt', 'dash_usdt', + 'xmr_usdt') + context.nassets = len(context.assets) + # Set the time window that will be used to compute expected return + # and asset correlations + context.window = 180 + # Set the number of days between each portfolio rebalancing + context.rebalance_period = 30 + context.i = 0 + - def handle_data(context, data): - # Only rebalance at the beggining of the algorithm execution and - # every multiple of the rebalance period - if context.i == 0 or context.i%context.rebalance_period == 0: - n = context.window - prices = data.history(context.assets, fields='price', - bar_count=n+1, frequency='1d') - pr = np.asmatrix(prices) - t_prices = prices.iloc[1:n+1] - t_val = t_prices.values - tminus_prices = prices.iloc[0:n] - tminus_val = tminus_prices.values - # Compute daily returns (r) - r = np.asmatrix(t_val/tminus_val-1) - # Compute the expected returns of each asset with the average - # daily return for the selected time window - m = np.asmatrix(np.mean(r, axis=0)) - # ### - stds = np.std(r, axis=0) - # Compute excess returns matrix (xr) - xr = r - m - # Matrix algebra to get variance-covariance matrix - cov_m = np.dot(np.transpose(xr),xr)/n - # Compute asset correlation matrix (informative only) - corr_m = cov_m/np.dot(np.transpose(stds),stds) - - # Define portfolio optimization parameters - n_portfolios = 50000 - results_array = np.zeros((3+context.nassets,n_portfolios)) - for p in xrange(n_portfolios): - weights = np.random.random(context.nassets) - weights /= np.sum(weights) - w = np.asmatrix(weights) - p_r = np.sum(np.dot(w,np.transpose(m)))*365 - p_std = np.sqrt(np.dot(np.dot(w,cov_m),np.transpose(w)))*np.sqrt(365) - - #store results in results array - results_array[0,p] = p_r - results_array[1,p] = p_std - #store Sharpe Ratio (return / volatility) - risk free rate element - #excluded for simplicity - results_array[2,p] = results_array[0,p] / results_array[1,p] - i = 0 - for iw in weights: - results_array[3+i,p] = weights[i] - i += 1 - - #convert results array to Pandas DataFrame - results_frame = pd.DataFrame(np.transpose(results_array), - columns=['r','stdev','sharpe']+context.assets) - #locate position of portfolio with highest Sharpe Ratio - max_sharpe_port = results_frame.iloc[results_frame['sharpe'].idxmax()] - #locate positon of portfolio with minimum standard deviation - min_vol_port = results_frame.iloc[results_frame['stdev'].idxmin()] - - #order optimal weights for each asset - for asset in context.assets: - if data.can_trade(asset): - order_target_percent(asset, max_sharpe_port[asset]) - - #create scatter plot coloured by Sharpe Ratio - plt.scatter(results_frame.stdev,results_frame.r,c=results_frame.sharpe,cmap='RdYlGn') - plt.xlabel('Volatility') - plt.ylabel('Returns') - plt.colorbar() - #plot red star to highlight position of portfolio with highest Sharpe Ratio - plt.scatter(max_sharpe_port[1],max_sharpe_port[0],marker='o',color='b',s=200) - #plot green star to highlight position of minimum variance portfolio - plt.show() - print(max_sharpe_port) - record(pr=pr,r=r, m=m, stds=stds ,max_sharpe_port=max_sharpe_port, corr_m=corr_m) - context.i += 1 - - + # Only rebalance at the beggining of the algorithm execution and + # every multiple of the rebalance period + if context.i == 0 or context.i % context.rebalance_period == 0: + n = context.window + prices = data.history(context.assets, fields='price', + bar_count=n+1, frequency='1d') + pr = np.asmatrix(prices) + t_prices = prices.iloc[1:n+1] + t_val = t_prices.values + tminus_prices = prices.iloc[0:n] + tminus_val = tminus_prices.values + # Compute daily returns (r) + r = np.asmatrix(t_val/tminus_val-1) + # Compute the expected returns of each asset with the average + # daily return for the selected time window + m = np.asmatrix(np.mean(r, axis=0)) + # ### + stds = np.std(r, axis=0) + # Compute excess returns matrix (xr) + xr = r - m + # Matrix algebra to get variance-covariance matrix + cov_m = np.dot(np.transpose(xr), xr)/n + # Compute asset correlation matrix (informative only) + corr_m = cov_m/np.dot(np.transpose(stds), stds) + + # Define portfolio optimization parameters + n_portfolios = 50000 + results_array = np.zeros((3+context.nassets, n_portfolios)) + for p in xrange(n_portfolios): + weights = np.random.random(context.nassets) + weights /= np.sum(weights) + w = np.asmatrix(weights) + p_r = np.sum(np.dot(w, np.transpose(m)))*365 + p_std = np.sqrt(np.dot(np.dot(w, cov_m), + np.transpose(w)))*np.sqrt(365) + + # store results in results array + results_array[0, p] = p_r + results_array[1, p] = p_std + # store Sharpe Ratio (return / volatility) - risk free rate element + # excluded for simplicity + results_array[2, p] = results_array[0, p] / results_array[1, p] + i = 0 + for iw in weights: + results_array[3+i, p] = weights[i] + i += 1 + + # convert results array to Pandas DataFrame + results_frame = pd.DataFrame(np.transpose(results_array), + columns=['r', 'stdev', 'sharpe'] + + context.assets) + # locate position of portfolio with highest Sharpe Ratio + max_sharpe_port = results_frame.iloc[results_frame['sharpe'].idxmax()] + # locate positon of portfolio with minimum standard deviation + # min_vol_port = results_frame.iloc[results_frame['stdev'].idxmin()] + + # order optimal weights for each asset + for asset in context.assets: + if data.can_trade(asset): + order_target_percent(asset, max_sharpe_port[asset]) + + # create scatter plot coloured by Sharpe Ratio + plt.scatter(results_frame.stdev, + results_frame.r, + c=results_frame.sharpe, + cmap='RdYlGn') + plt.xlabel('Volatility') + plt.ylabel('Returns') + plt.colorbar() + # plot red star to highlight position of portfolio + # with highest Sharpe Ratio + plt.scatter(max_sharpe_port[1], + max_sharpe_port[0], + marker='o', + color='b', + s=200) + # plot green star to highlight position of minimum variance portfolio + plt.show() + print(max_sharpe_port) + record(pr=pr, + r=r, + m=m, + stds=stds, + max_sharpe_port=max_sharpe_port, + corr_m=corr_m) + context.i += 1 + + def analyze(context=None, results=None): - # Form DataFrame with selected data - data = results[['pr','r','m','stds','max_sharpe_port','corr_m','portfolio_value']] - - # Save results in CSV file - filename = os.path.splitext(os.path.basename(__file__))[0] - data.to_csv(filename + '.csv') + # Form DataFrame with selected data + data = results[['pr', 'r', 'm', 'stds', 'max_sharpe_port', 'corr_m', + 'portfolio_value']] + + # Save results in CSV file + filename = os.path.splitext(os.path.basename(__file__))[0] + data.to_csv(filename + '.csv') -# Bitcoin data is available from 2015-3-2. Dates vary for other tokens. +# Bitcoin data is available from 2015-3-2. Dates vary for other tokens. start = datetime(2017, 1, 1, 0, 0, 0, 0, pytz.utc) -end = datetime(2017, 8, 16, 0, 0, 0, 0, pytz.utc) +end = datetime(2017, 8, 16, 0, 0, 0, 0, pytz.utc) results = run_algorithm(initialize=initialize, handle_data=handle_data, analyze=analyze, diff --git a/catalyst/examples/rsi_profit_target.py b/catalyst/examples/rsi_profit_target.py index 5e24f122..7b8ac868 100644 --- a/catalyst/examples/rsi_profit_target.py +++ b/catalyst/examples/rsi_profit_target.py @@ -11,7 +11,6 @@ from catalyst.api import ( record, get_open_orders, ) -from catalyst.exchange.stats_utils import crossover, crossunder from catalyst.utils.run_algo import run_algorithm algo_namespace = 'rsi' @@ -55,7 +54,7 @@ def _handle_buy_sell_decision(context, data, signal, price): stop=None ) - action = None + # action = None if context.position is not None: cost_basis = context.position['cost_basis'] amount = context.position['amount'] @@ -80,7 +79,7 @@ def _handle_buy_sell_decision(context, data, signal, price): amount=-amount, limit_price=price * (1 - context.SLIPPAGE_ALLOWED), ) - action = 0 + # action = 0 context.position = None else: @@ -97,7 +96,7 @@ def _handle_buy_sell_decision(context, data, signal, price): amount=buy_amount, stop=None ) - action = 0 + # action = 0 def _handle_data_rsi_only(context, data): diff --git a/catalyst/examples/simple_universe.py b/catalyst/examples/simple_universe.py index a0abc535..7539a76f 100644 --- a/catalyst/examples/simple_universe.py +++ b/catalyst/examples/simple_universe.py @@ -2,26 +2,26 @@ Requires Catalyst version 0.3.0 or above Tested on Catalyst version 0.3.3 -This example aims to provide an easy way for users to learn how to +This example aims to provide an easy way for users to learn how to collect data from any given exchange and select a subset of the available -currency pairs for trading. You simply need to specify the exchange and -the market (base_currency) that you want to focus on. You will then see -how to create a universe of assets, and filter it based the market you +currency pairs for trading. You simply need to specify the exchange and +the market (base_currency) that you want to focus on. You will then see +how to create a universe of assets, and filter it based the market you desire. -The example prints out the closing price of all the pairs for a given -market in a given exchange every 30 minutes. The example also contains -the OHLCV data with minute-resolution for the past seven days which -could be used to create indicators. Use this code as the backbone to +The example prints out the closing price of all the pairs for a given +market in a given exchange every 30 minutes. The example also contains +the OHLCV data with minute-resolution for the past seven days which +could be used to create indicators. Use this code as the backbone to create your own trading strategy. -The lookback_date variable is used to ensure data for a coin existed on +The lookback_date variable is used to ensure data for a coin existed on the lookback period specified. -To run, execute the following two commands in a terminal (inside catalyst +To run, execute the following two commands in a terminal (inside catalyst environment). The first one retrieves all the pricing data needed for this script to run (only needs to be run once), and the second one executes this -script with the parameters specified in the run_algorithm() call at the end +script with the parameters specified in the run_algorithm() call at the end of the file: catalyst ingest-exchange -x bitfinex -f minute @@ -41,8 +41,8 @@ from catalyst.api import (symbols, ) def initialize(context): context.i = -1 # minute counter - context.exchange = context.exchanges.values()[0].name.lower() - context.base_currency = context.exchanges.values()[0].base_currency.lower() + context.exchange = context.exchanges.values()[0].name.lower() + context.base_currency = context.exchanges.values()[0].base_currency.lower() def handle_data(context, data): @@ -52,9 +52,9 @@ def handle_data(context, data): # current date & time in each iteration formatted into a string now = data.current_dt date, time = now.strftime('%Y-%m-%d %H:%M:%S').split(' ') - lookback_date = now - timedelta(days=lookback_days) + lookback_date = now - timedelta(days=lookback_days) # keep only the date as a string, discard the time - lookback_date = lookback_date.strftime('%Y-%m-%d %H:%M:%S').split(' ')[0] + lookback_date = lookback_date.strftime('%Y-%m-%d %H:%M:%S').split(' ')[0] one_day_in_minutes = 1440 # 60 * 24 assumes data_frequency='minute' # update universe everyday at midnight @@ -64,39 +64,50 @@ def handle_data(context, data): # get data every 30 minutes minutes = 30 # get lookback_days of history data: that is 'lookback' number of bins - lookback = one_day_in_minutes / minutes * lookback_days + lookback = one_day_in_minutes / minutes * lookback_days if not context.i % minutes and context.universe: # we iterate for every pair in the current universe for coin in context.coins: pair = str(coin.symbol) - # Get 30 minute interval OHLCV data. This is the standard data + # Get 30 minute interval OHLCV data. This is the standard data # required for candlestick or indicators/signals. Return Pandas - # DataFrames. 30T means 30-minute re-sampling of one minute data. + # DataFrames. 30T means 30-minute re-sampling of one minute data. # Adjust it to your desired time interval as needed. - opened = fill(data.history(coin, 'open', - bar_count=lookback, frequency='30T')).values - high = fill(data.history(coin, 'high', - bar_count=lookback, frequency='30T')).values - low = fill(data.history(coin, 'low', - bar_count=lookback, frequency='30T')).values - close = fill(data.history(coin, 'price', - bar_count=lookback, frequency='30T')).values - volume = fill(data.history(coin, 'volume', - bar_count=lookback, frequency='30T')).values + opened = fill(data.history(coin, + 'open', + bar_count=lookback, + frequency='30T')).values + high = fill(data.history(coin, + 'high', + bar_count=lookback, + frequency='30T')).values + low = fill(data.history(coin, + 'low', + bar_count=lookback, + frequency='30T')).values + close = fill(data.history(coin, + 'price', + bar_count=lookback, + frequency='30T')).values + volume = fill(data.history(coin, + 'volume', + bar_count=lookback, + frequency='30T')).values - # close[-1] is the last value in the set, which is the equivalent + # close[-1] is the last value in the set, which is the equivalent # to current price (as in the most recent value) # displays the minute price for each pair every 30 minutes - print('{now}: {pair} -\tO:{o},\tH:{h},\tL:{c},\tC{c},\tV:{v}'.format( - now=now, - pair=pair, - o=opened[-1], - h=high[-1], + print('{now}: {pair} -\tO:{o},\tH:{h},\tL:{c},\tC{c},' + '\tV:{v}'.format( + now=now, + pair=pair, + o=opened[-1], + h=high[-1], l=low[-1], c=close[-1], v=volume[-1], - )) + )) # ------------------------------------------------------------- # --------------- Insert Your Strategy Here ------------------- @@ -111,16 +122,18 @@ def analyze(context=None, results=None): # Example: Poloniex BTC Market def universe(context, lookback_date, current_date): # get all the pairs for the given exchange - json_symbols = get_exchange_symbols(context.exchange) + json_symbols = get_exchange_symbols(context.exchange) # convert into a DataFrame for easier processing - df = pd.DataFrame.from_dict(json_symbols).transpose().astype(str) - df['base_currency'] = df.apply(lambda row: row.symbol.split('_')[1],axis=1) - df['market_currency'] = df.apply(lambda row: row.symbol.split('_')[0],axis=1) + df = pd.DataFrame.from_dict(json_symbols).transpose().astype(str) + df['base_currency'] = df.apply(lambda row: row.symbol.split('_')[1], + axis=1) + df['market_currency'] = df.apply(lambda row: row.symbol.split('_')[0], + axis=1) # Filter all the pairs to get only the ones for a given base_currency df = df[df['base_currency'] == context.base_currency] - # Filter all the pairs to ensure that pair existed in the current date range + # Filter all pairs to ensure that pair existed in the current date range df = df[df.start_date < lookback_date] df = df[df.end_daily >= current_date] context.coins = symbols(*df.symbol) # convert all the pairs to symbols @@ -155,4 +168,3 @@ if __name__ == '__main__': live=False, live_graph=False, algo_namespace='simple_universe') - diff --git a/catalyst/examples/talib_simple.py b/catalyst/examples/talib_simple.py index d4a7d807..5129dda9 100644 --- a/catalyst/examples/talib_simple.py +++ b/catalyst/examples/talib_simple.py @@ -1,9 +1,11 @@ # Run Command -# catalyst run --start 2017-1-1 --end 2017-11-1 -o talib_simple.pickle -f talib_simple.py -x poloniex -# +# catalyst run --start 2017-1-1 --end 2017-11-1 -o talib_simple.pickle \ +# -f talib_simple.py -x poloniex +# # Description -# Simple TALib Example showing how to use various indicators in you strategy -# Based loosly on https://github.com/mellertson/talib-macd-example/blob/master/talib-macd-matplotlib-example.py +# Simple TALib Example showing how to use various indicators +# in you strategy. Based loosly on +# https://github.com/mellertson/talib-macd-example/blob/master/talib-macd-matplotlib-example.py import os @@ -88,7 +90,7 @@ def _handle_data(context, data): prices.close.as_matrix(), fastperiod=context.MACD_FAST, slowperiod=context.MACD_SLOW, signalperiod=context.MACD_SIGNAL) - # Stochastics %K %D + # Stochastics %K %D # %K = (Current Close - Lowest Low)/(Highest High - Lowest Low) * 100 # %D = 3-day SMA of %K analysis['stoch_k'], analysis['stoch_d'] = ta.STOCH( diff --git a/catalyst/exchange/bitfinex/bitfinex.py b/catalyst/exchange/bitfinex/bitfinex.py index 12c051cd..fa6077ac 100644 --- a/catalyst/exchange/bitfinex/bitfinex.py +++ b/catalyst/exchange/bitfinex/bitfinex.py @@ -14,6 +14,7 @@ import six from catalyst.assets._assets import TradingPair from logbook import Logger +from catalyst.constants import LOG_LEVEL from catalyst.exchange.exchange import Exchange from catalyst.exchange.exchange_bundle import ExchangeBundle from catalyst.exchange.exchange_errors import ( @@ -33,7 +34,6 @@ requests.adapters.DEFAULT_RETRIES = 20 BITFINEX_URL = 'https://api.bitfinex.com' -from catalyst.constants import LOG_LEVEL log = Logger('Bitfinex', level=LOG_LEVEL) warning_logger = Logger('AlgoWarning') @@ -172,7 +172,8 @@ class Bitfinex(Exchange): executed_price = float(order_status['avg_execution_price']) - # TODO: bitfinex does not specify comission. I could calculate it but not sure if it's worth it. + # TODO: bitfinex does not specify comission. + # I could calculate it but not sure if it's worth it. commission = None date = pd.Timestamp.utcfromtimestamp(float(order_status['timestamp'])) @@ -599,17 +600,17 @@ class Bitfinex(Exchange): else: try: start_date = cached_symbols[symbol]['start_date'] - except KeyError as e: + except KeyError: start_date = time.strftime('%Y-%m-%d') try: end_daily = cached_symbols[symbol]['end_daily'] - except KeyError as e: + except KeyError: end_daily = 'N/A' try: end_minute = cached_symbols[symbol]['end_minute'] - except KeyError as e: + except KeyError: end_minute = 'N/A' symbol_map[symbol] = dict( @@ -660,15 +661,16 @@ class Bitfinex(Exchange): """ Query again with daily resolution setting the start and end around - the startmonth we got above. Avoid end dates greater than now: time.time() + the startmonth we got above. Avoid end dates greater than + now: time.time() """ - url = '{url}/v2/candles/trade:1D:{symbol}/hist?start={start}&end={end}'.format( - url=self.url, - symbol=symbol_v2, - start=startmonth - 3600 * 24 * 31 * 1000, - end=min(startmonth + 3600 * 24 * 31 * 1000, - int(time.time() * 1000)) - ) + url = ('{url}/v2/candles/trade:1D:{symbol}/hist?start={start}' + '&end={end}').format( + url=self.url, + symbol=symbol_v2, + start=startmonth - 3600 * 24 * 31 * 1000, + end=min(startmonth + 3600 * 24 * 31 * 1000, + int(time.time() * 1000))) try: self.ask_request() diff --git a/catalyst/exchange/bittrex/bittrex.py b/catalyst/exchange/bittrex/bittrex.py index af85aef1..5835b8e1 100644 --- a/catalyst/exchange/bittrex/bittrex.py +++ b/catalyst/exchange/bittrex/bittrex.py @@ -262,11 +262,10 @@ class Bittrex(Exchange): end = int(time.mktime(end_dt.timetuple())) url = '{url}/pub/market/GetTicks?marketName={symbol}' \ '&tickInterval={frequency}&_={end}'.format( - url=URL2, - symbol=self.get_symbol(asset), - frequency=frequency, - end=end - ) + url=URL2, + symbol=self.get_symbol(asset), + frequency=frequency, + end=end, ) try: data = json.loads(urllib.request.urlopen(url).read().decode()) @@ -359,12 +358,12 @@ class Bittrex(Exchange): try: end_daily = cached_symbols[exchange_symbol]['end_daily'] - except KeyError as e: + except KeyError: end_daily = 'N/A' try: end_minute = cached_symbols[exchange_symbol]['end_minute'] - except KeyError as e: + except KeyError: end_minute = 'N/A' symbol_map[exchange_symbol] = dict( diff --git a/catalyst/exchange/bittrex/extensions-example.py b/catalyst/exchange/bittrex/extensions-example.py index 33ffb4f5..4b087899 100644 --- a/catalyst/exchange/bittrex/extensions-example.py +++ b/catalyst/exchange/bittrex/extensions-example.py @@ -4,4 +4,4 @@ from catalyst.exchange.exchange_bundle import exchange_bundle symbols = ( 'neo_btc', ) -register('exchange_bitfinex', exchange_bundle('bitfinex', symbols)) \ No newline at end of file +register('exchange_bitfinex', exchange_bundle('bitfinex', symbols)) diff --git a/catalyst/exchange/bundle_utils.py b/catalyst/exchange/bundle_utils.py index 29509bfa..0d758c7d 100644 --- a/catalyst/exchange/bundle_utils.py +++ b/catalyst/exchange/bundle_utils.py @@ -6,11 +6,9 @@ from datetime import timedelta, datetime, date import numpy as np import pandas as pd import pytz -from catalyst.assets._assets import TradingPair from catalyst.data.bundles.core import download_without_progress -from catalyst.exchange.exchange_utils import get_exchange_bundles_folder, \ - get_exchange_symbols +from catalyst.exchange.exchange_utils import get_exchange_bundles_folder EXCHANGE_NAMES = ['bitfinex', 'bittrex', 'poloniex'] API_URL = 'http://data.enigma.co/api/v1' @@ -80,9 +78,8 @@ def get_bcolz_chunk(exchange_name, symbol, data_frequency, period): if not os.path.isdir(path): url = 'https://s3.amazonaws.com/enigmaco/catalyst-bundles/' \ 'exchange-{exchange}/{name}.tar.gz'.format( - exchange=exchange_name, - name=name - ) + exchange=exchange_name, + name=name) bytes = download_without_progress(url) with tarfile.open('r', fileobj=bytes) as tar: @@ -193,8 +190,10 @@ def get_period_label(dt, data_frequency): str """ - return '{}-{:02d}'.format(dt.year, dt.month) if data_frequency == 'minute' \ - else '{}'.format(dt.year) + if data_frequency == 'minute': + return '{}-{:02d}'.format(dt.year, dt.month) + else: + return '{}'.format(dt.year) def get_month_start_end(dt, first_day=None, last_day=None): @@ -315,7 +314,7 @@ def range_in_bundle(asset, start_dt, end_dt, reader): if np.isnan(close): has_data = False - except Exception as e: + except Exception: has_data = False return has_data diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index 642d3612..3f2ac85a 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -5,7 +5,6 @@ from time import sleep import numpy as np import pandas as pd -from catalyst.assets._assets import TradingPair from logbook import Logger from catalyst.constants import LOG_LEVEL @@ -242,9 +241,7 @@ class Exchange: asset = a if asset is None: - supported_symbols = sorted([ - asset.symbol for asset in self.assets - ]) + supported_symbols = sorted([a.symbol for a in self.assets]) raise SymbolNotFoundOnExchange( symbol=symbol, @@ -551,7 +548,7 @@ class Exchange: start_dt = get_start_dt(end_dt, adj_bar_count, data_frequency) trailing_dt = \ series[asset].index[-1] + get_delta(1, data_frequency) \ - if asset in series else start_dt + if asset in series else start_dt # The get_history method supports multiple asset # Use the original frequency to let each api optimize @@ -693,7 +690,8 @@ class Exchange: display_price = style.get_limit_price(is_buy) log.debug( - 'issuing {side} order of {amount} {symbol} for {type}: {price}'.format( + 'issuing {side} order of {amount} {symbol} for {type}:' + ' {price}'.format( side='buy' if is_buy else 'sell', amount=amount, symbol=asset.symbol, diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index 147d87e4..adf76d5d 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -29,19 +29,22 @@ from catalyst.exchange.exchange_blotter import ExchangeBlotter from catalyst.exchange.exchange_errors import ( ExchangeRequestError, ExchangePortfolioDataError, - OrderTypeNotSupported) + OrderTypeNotSupported, ) from catalyst.exchange.exchange_execution import ExchangeLimitOrder -from catalyst.exchange.exchange_utils import save_algo_object, get_algo_object, \ - get_algo_folder, get_algo_df, \ - save_algo_df, group_assets_by_exchange +from catalyst.exchange.exchange_utils import ( + save_algo_object, + get_algo_object, + get_algo_folder, + get_algo_df, + save_algo_df, + group_assets_by_exchange, ) from catalyst.exchange.live_graph_clock import LiveGraphClock from catalyst.exchange.simple_clock import SimpleClock from catalyst.exchange.stats_utils import get_pretty_stats, stats_to_s3 from catalyst.finance.execution import MarketOrder from catalyst.finance.performance.period import calc_period_stats from catalyst.gens.tradesimulation import AlgorithmSimulator -from catalyst.utils.api_support import ( - api_method) +from catalyst.utils.api_support import api_method from catalyst.utils.input_validation import error_keywords, ensure_upper_case from catalyst.utils.math_utils import round_nearest from catalyst.utils.preprocess import preprocess @@ -394,7 +397,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): # This method is taken from TradingAlgorithm. # The clock has been replaced to use RealtimeClock - # TODO: should we apply a time skew? not sure to understand the utility. + # TODO: should we apply time skew? not sure to understand the utility. log.debug('creating clock') if self.live_graph: @@ -616,7 +619,8 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): # print_df = pd.DataFrame(list(self.frame_stats)) log.info( - 'statistics for the last {stats_minutes} minutes:\n{stats}'.format( + 'statistics for the last {stats_minutes} minutes:\n' + '{stats}'.format( stats_minutes=self.stats_minutes, stats=get_pretty_stats( stats=self.frame_stats, diff --git a/catalyst/exchange/exchange_bundle.py b/catalyst/exchange/exchange_bundle.py index 59e5ce90..46c0c9e7 100644 --- a/catalyst/exchange/exchange_bundle.py +++ b/catalyst/exchange/exchange_bundle.py @@ -1,6 +1,6 @@ import os import shutil -from datetime import datetime, timedelta +from datetime import timedelta from functools import partial from itertools import chain from operator import is_not @@ -233,11 +233,13 @@ class ExchangeBundle: problem = '{name} ({start_dt} to {end_dt}) has empty ' \ 'periods: {dates}'.format( - name=asset.symbol, - start_dt=asset.start_date.strftime(DATE_TIME_FORMAT), - end_dt=end_dt.strftime(DATE_TIME_FORMAT), - dates=[date.strftime(DATE_TIME_FORMAT) for date in dates] - ) + name=asset.symbol, + start_dt=asset.start_date.strftime( + DATE_TIME_FORMAT), + end_dt=end_dt.strftime(DATE_TIME_FORMAT), + dates=[date.strftime( + DATE_TIME_FORMAT) for date in dates]) + if empty_rows_behavior == 'warn': log.warn(problem) @@ -245,8 +247,7 @@ class ExchangeBundle: raise EmptyValuesInBundleError( name=asset.symbol, end_minute=end_dt, - dates=dates - ) + dates=dates, ) else: ohlcv_df.dropna(inplace=True) @@ -286,13 +287,12 @@ class ExchangeBundle: problem = '{name} ({start_dt} to {end_dt}) has {threshold} ' \ 'identical close values on: {dates}'.format( - name=asset.symbol, - start_dt=asset.start_date.strftime(DATE_TIME_FORMAT), - end_dt=end_dt.strftime(DATE_TIME_FORMAT), - threshold=threshold, - dates=[pd.to_datetime(date).strftime(DATE_TIME_FORMAT) - for date in dates] - ) + name=asset.symbol, + start_dt=asset.start_date.strftime(DATE_TIME_FORMAT), + end_dt=end_dt.strftime(DATE_TIME_FORMAT), + threshold=threshold, + dates=[pd.to_datetime(date).strftime(DATE_TIME_FORMAT) + for date in dates]) problems.append(problem) @@ -630,8 +630,8 @@ class ExchangeBundle: show_progress, label='Ingesting {frequency} price data on ' '{exchange}'.format( - exchange=self.exchange_name, - frequency=data_frequency, + exchange=self.exchange_name, + frequency=data_frequency, )) as it: for chunk in it: problems += self.ingest_ctable( diff --git a/catalyst/exchange/exchange_errors.py b/catalyst/exchange/exchange_errors.py index 7bf66f6c..4f1acd9c 100644 --- a/catalyst/exchange/exchange_errors.py +++ b/catalyst/exchange/exchange_errors.py @@ -143,7 +143,8 @@ class OrphanOrderError(ZiplineError): class OrphanOrderReverseError(ZiplineError): msg = ( - 'Order {order_id} tracked by algorithm, but not found in exchange {exchange}.' + 'Order {order_id} tracked by algorithm, but not found in exchange ' + '{exchange}.' ).strip() @@ -206,8 +207,9 @@ class EmptyValuesInBundleError(ZiplineError): class PricingDataBeforeTradingError(ZiplineError): msg = ('Pricing data for trading pairs {symbols} on exchange {exchange} ' - 'starts on {first_trading_day}, but you are either trying to trade or ' - 'retrieve pricing data on {dt}. Adjust your dates accordingly.').strip() + 'starts on {first_trading_day}, but you are either trying to trade ' + 'or retrieve pricing data on {dt}. Adjust your dates accordingly.' + ).strip() class PricingDataNotLoadedError(ZiplineError): @@ -238,9 +240,11 @@ class ApiCandlesError(ZiplineError): class NoDataAvailableOnExchange(ZiplineError): msg = ( - 'Requested data for trading pair {symbol} is not available on exchange {exchange} ' + 'Requested data for trading pair {symbol} is not available on ' + 'exchange {exchange} ' 'in `{data_frequency}` frequency at this time. ' - 'Check `http://enigma.co/catalyst/status` for market coverage.').strip() + 'Check `http://enigma.co/catalyst/status` for market coverage.' + ).strip() class NoValueForField(ZiplineError): diff --git a/catalyst/exchange/exchange_execution.py b/catalyst/exchange/exchange_execution.py index fe029e3c..536b526a 100644 --- a/catalyst/exchange/exchange_execution.py +++ b/catalyst/exchange/exchange_execution.py @@ -1,4 +1,4 @@ -from catalyst.finance.execution import LimitOrder, StopOrder, StopLimitOrder, MarketOrder +from catalyst.finance.execution import LimitOrder, StopOrder, StopLimitOrder class ExchangeLimitOrder(LimitOrder): diff --git a/catalyst/exchange/exchange_portfolio.py b/catalyst/exchange/exchange_portfolio.py index 1df8f9b2..71ed9a35 100644 --- a/catalyst/exchange/exchange_portfolio.py +++ b/catalyst/exchange/exchange_portfolio.py @@ -90,7 +90,8 @@ class ExchangePortfolio(Portfolio): if order_position is None: raise ValueError( - 'Trying to execute order for a position not held: %s' % order.id + 'Trying to execute order for a position not held:' + ' {}'.format(order.id) ) self.capital_used += order.amount * transaction.price diff --git a/catalyst/exchange/exchange_utils.py b/catalyst/exchange/exchange_utils.py index af7f2fee..19938d3e 100644 --- a/catalyst/exchange/exchange_utils.py +++ b/catalyst/exchange/exchange_utils.py @@ -134,7 +134,7 @@ def get_exchange_symbols(exchange_name, is_local=False, environ=None): if not is_local and (not os.path.isfile(filename) or pd.Timedelta( pd.Timestamp('now', tz='UTC') - last_modified_time( - filename)).days > 1): + filename)).days > 1): download_exchange_symbols(exchange_name, environ) if os.path.isfile(filename): @@ -143,7 +143,7 @@ def get_exchange_symbols(exchange_name, is_local=False, environ=None): data = json.load(data_file, object_hook=symbols_parser) return data - except ValueError as e: + except ValueError: return dict() else: raise ExchangeSymbolsNotFound( @@ -296,7 +296,7 @@ def get_algo_object(algo_name, key, environ=None, rel_path=None): try: with open(filename, 'rb') as handle: return pickle.load(handle) - except Exception as e: + except Exception: return None else: return None diff --git a/catalyst/exchange/poloniex/poloniex.py b/catalyst/exchange/poloniex/poloniex.py index 1c2028ee..b29b0e92 100644 --- a/catalyst/exchange/poloniex/poloniex.py +++ b/catalyst/exchange/poloniex/poloniex.py @@ -1,5 +1,4 @@ import json -import json import time from collections import defaultdict @@ -18,7 +17,9 @@ from catalyst.exchange.exchange_bundle import ExchangeBundle from catalyst.exchange.exchange_errors import ( ExchangeRequestError, InvalidHistoryFrequencyError, - InvalidOrderStyle, OrphanOrderReverseError) + InvalidOrderStyle, + OrphanOrderError, + OrphanOrderReverseError) from catalyst.exchange.exchange_execution import ExchangeLimitOrder, \ ExchangeStopLimitOrder from catalyst.exchange.exchange_utils import get_exchange_symbols_filename, \ @@ -87,7 +88,6 @@ class Poloniex(Exchange): # filled = -filled price = float(order_status['rate']) - order_type = order_status['type'] stop_price = None limit_price = None @@ -101,11 +101,11 @@ class Poloniex(Exchange): # executed_price = float(order_status['avg_execution_price']) executed_price = price - # TODO: bitfinex does not specify comission. I could calculate it but not sure if it's worth it. + # TODO: Set Poloniex comission commission = None - # date = pd.Timestamp.utcfromtimestamp(float(order_status['timestamp'])) - # date = pytz.utc.localize(date) + # date=pd.Timestamp.utcfromtimestamp(float(order_status['timestamp'])) + # date=pytz.utc.localize(date) date = None order = Order( @@ -292,8 +292,8 @@ class Poloniex(Exchange): """ exchange_symbol = self.get_symbol(asset) - if isinstance(style, ExchangeLimitOrder) or isinstance(style, - ExchangeStopLimitOrder): + if(isinstance(style, ExchangeLimitOrder) + or isinstance(style, ExchangeStopLimitOrder)): if isinstance(style, ExchangeStopLimitOrder): log.warn('{} will ignore the stop price'.format(self.name)) @@ -350,8 +350,8 @@ class Poloniex(Exchange): return self.portfolio.open_orders """ - TODO: Why going to the exchange if we already have this info locally? - And why creating all these Orders if we later discard them? + TODO: Why going to the exchange if we already have this info locally? + And why creating all these Orders if we later discard them? """ try: @@ -365,7 +365,7 @@ class Poloniex(Exchange): if 'error' in response: raise ExchangeRequestError( error='Unable to retrieve open orders: {}'.format( - order_statuses['message']) + response['message']) ) print(self.portfolio.open_orders) @@ -373,8 +373,8 @@ class Poloniex(Exchange): # TODO: Need to handle openOrders for 'all' orders = list() for order_status in response: - order, executed_price = self._create_order( - order_status) # will Throw error b/c Polo doesn't track order['symbol'] + # will Throw error b/c Polo doesn't track order['symbol'] + order, executed_price = self._create_order(order_status) if asset is None or asset == order.sid: orders.append(order) @@ -437,7 +437,8 @@ class Poloniex(Exchange): if 'error' in response: log.info( - 'Unable to cancel order {order_id} on exchange {exchange} {error}.'.format( + 'Unable to cancel order {order_id} on exchange {exchange} ' + '{error}.'.format( order_id=order.id, exchange=self.name, error=response['error'] @@ -512,17 +513,17 @@ class Poloniex(Exchange): else: try: start_date = cached_symbols[exchange_symbol]['start_date'] - except KeyError as e: + except KeyError: start_date = time.strftime('%Y-%m-%d') try: end_daily = cached_symbols[exchange_symbol]['end_daily'] - except KeyError as e: + except KeyError: end_daily = 'N/A' try: end_minute = cached_symbols[exchange_symbol]['end_minute'] - except KeyError as e: + except KeyError: end_minute = 'N/A' symbol_map[exchange_symbol] = dict( @@ -593,19 +594,21 @@ class Poloniex(Exchange): else: for tx in response: """ - We maintain a list of dictionaries of transactions that correspond to - partially filled orders, indexed by order_id. Every time we query - executed transactions from the exchange, we check if we had that - transaction for that order already. If not, we process it. + We maintain a list of dictionaries of transactions that + correspond to partially filled orders, indexed by + order_id. Every time we query executed transactions + from the exchange, we check if we had that transaction + for that order already. If not, we process it. - When an order if fully filled, we flush the dict of transactions - associated with that order. + When an order if fully filled, we flush the dict of + transactions associated with that order. """ if (not filter( lambda item: item['order_id'] == tx['tradeID'], self.transactions[order_id])): log.debug( - 'Got new transaction for order {}: amount {}, price {}'.format( + 'Got new transaction for order {}: amount {}, ' + 'price {}'.format( order_id, tx['amount'], tx['rate'])) tx['amount'] = float(tx['amount']) if (tx['type'] == 'sell'): @@ -616,7 +619,7 @@ class Poloniex(Exchange): dt=pd.to_datetime(tx['date'], utc=True), price=float(tx['rate']), order_id=tx['tradeID'], - # it's a misnomer, but keeping it for compatibility + # it's a misnomer, but keep for compatibility commission=float(tx['fee']) ) self.transactions[order_id].append(transaction) @@ -626,7 +629,8 @@ class Poloniex(Exchange): if (not order_open): """ Since transactions have been executed individually - the only thing left to do is remove them from list of open_orders + the only thing left to do is remove them from list + of open_orders """ del self.portfolio.open_orders[order_id] del self.transactions[order_id] diff --git a/catalyst/exchange/poloniex/poloniex_api.py b/catalyst/exchange/poloniex/poloniex_api.py index ce0831bc..6f339192 100644 --- a/catalyst/exchange/poloniex/poloniex_api.py +++ b/catalyst/exchange/poloniex/poloniex_api.py @@ -108,7 +108,7 @@ class Poloniex_api(object): headers=headers, ) resource = urlopen(req, context=ssl._create_unverified_context()) - content = resource.read().decode('utf-8') + content = resource.read().decode('utf-8') return json.loads(content) def returnticker(self): @@ -161,10 +161,6 @@ class Poloniex_api(object): def returnopenorders(self, market): return self.query('returnOpenOrders', {'currencyPair': market}) - def returntradehistory(self, market): - # TODO: optional start and/or end and limit - return self.query('returnTradeHistory', {'currencyPair': market}) - def returnordertrades(self, ordernumber): return self.query('returnOrderTrades', {'orderNumber': ordernumber}) @@ -177,7 +173,7 @@ class Poloniex_api(object): elif (immediateorcancel): return self.query('buy', {'currencyPair': market, 'rate': rate, 'amount': amount, - 'immediateOrCancel': immediateorcancel, }) + 'immediateOrCancel': immediateorcancel}) elif (postonly): return self.query('buy', {'currencyPair': market, 'rate': rate, 'amount': amount, @@ -195,7 +191,7 @@ class Poloniex_api(object): elif (immediateorcancel): return self.query('sell', {'currencyPair': market, 'rate': rate, 'amount': amount, - 'immediateOrCancel': immediateorcancel, }) + 'immediateOrCancel': immediateorcancel}) elif (postonly): return self.query('sell', {'currencyPair': market, 'rate': rate, 'amount': amount, diff --git a/catalyst/exchange/simple_clock.py b/catalyst/exchange/simple_clock.py index f79a5ab7..cede9429 100644 --- a/catalyst/exchange/simple_clock.py +++ b/catalyst/exchange/simple_clock.py @@ -31,7 +31,8 @@ class SimpleClock(object): This class is a drop-in replacement for :class:`zipline.gens.sim_engine.MinuteSimulationClock`. - This is a stripped down version because crypto exchanges run around the clock. + This is a stripped down version because crypto exchanges run + around the clock. The :param:`time_skew` parameter represents the time difference between the Broker and the live trading machine's clock. diff --git a/catalyst/finance/execution.py b/catalyst/finance/execution.py index 8fec0af6..f4fe95fe 100644 --- a/catalyst/finance/execution.py +++ b/catalyst/finance/execution.py @@ -15,13 +15,8 @@ import abc -from sys import float_info - -from six import with_metaclass - -import catalyst.utils.math_utils as zp_math - from numpy import isfinite +from six import with_metaclass from catalyst.errors import BadOrderParameters diff --git a/catalyst/finance/risk/period.py b/catalyst/finance/risk/period.py index 283bd3c5..987b8d72 100644 --- a/catalyst/finance/risk/period.py +++ b/catalyst/finance/risk/period.py @@ -154,8 +154,8 @@ class RiskMetricsPeriod(object): self.algorithm_returns.values, self.benchmark_returns.values, ) - self.excess_return = self.algorithm_period_returns - \ - self.treasury_period_return + self.excess_return = self.algorithm_period_returns \ + - self.treasury_period_return self.max_drawdown = max_drawdown(self.algorithm_returns.values) self.max_leverage = self.calculate_max_leverage() diff --git a/catalyst/finance/risk/risk.py b/catalyst/finance/risk/risk.py index e447617e..07c29030 100644 --- a/catalyst/finance/risk/risk.py +++ b/catalyst/finance/risk/risk.py @@ -160,7 +160,8 @@ def choose_treasury(select_treasury, treasury_curves, start_session, ) break - if search_day and trading_calendar.name != 'OPEN': # Supress warning for 'OPEN' calendar + # Supress warning for 'OPEN' calendar + if search_day and trading_calendar.name != 'OPEN': if (search_dist is None or search_dist > 1) and \ search_days[0] <= end_session <= search_days[-1]: message = "No rate within 1 trading day of end date = \ diff --git a/catalyst/finance/slippage.py b/catalyst/finance/slippage.py index 36de4ec7..98784f50 100644 --- a/catalyst/finance/slippage.py +++ b/catalyst/finance/slippage.py @@ -41,7 +41,6 @@ DEFAULT_EQUITY_VOLUME_SLIPPAGE_BAR_LIMIT = 0.025 DEFAULT_FUTURE_VOLUME_SLIPPAGE_BAR_LIMIT = 0.05 - class LiquidityExceeded(Exception): pass diff --git a/catalyst/pipeline/factors/equity/__init__.py b/catalyst/pipeline/factors/equity/__init__.py index 663d025f..e8d29cf0 100644 --- a/catalyst/pipeline/factors/equity/__init__.py +++ b/catalyst/pipeline/factors/equity/__init__.py @@ -1,9 +1,6 @@ from .statistical import ( - RollingPearson, - RollingLinearRegression, RollingLinearRegressionOfReturns, RollingPearsonOfReturns, - RollingSpearman, RollingSpearmanOfReturns, ) from .technical import ( diff --git a/catalyst/pipeline/loaders/equity_pricing_loader.py b/catalyst/pipeline/loaders/equity_pricing_loader.py index c7dbfd41..648bd5f5 100644 --- a/catalyst/pipeline/loaders/equity_pricing_loader.py +++ b/catalyst/pipeline/loaders/equity_pricing_loader.py @@ -38,9 +38,11 @@ class USEquityPricingLoader(PipelineLoader): def __init__(self, bundle, data_frequency, dataset): - if data_frequency == 'daily': - reader = bundle.daily_bar_reader - elif daily_bar_reader == 'minute': + # TODO: This is currently broken, No Pipeline support for Catalyst + # if data_frequency == 'daily': + # reader = bundle.daily_bar_reader + # elif daily_bar_reader == 'minute': + if data_frequency == 'minute': reader = bundle.minute_bar_reader else: raise ValueError( @@ -51,7 +53,9 @@ class USEquityPricingLoader(PipelineLoader): if data_frequency == 'daily': all_sessions = cal.all_sessions - elif daily_bar_reader == 'minute': + # TODO: this cannot be right, but no pipeline support at the moment + # elif daily_bar_reader == 'minute': + elif data_frequency == 'minute': reader = bundle.minute_bar_reader all_sessions = cal.all_minutes diff --git a/catalyst/pipeline/loaders/events.py b/catalyst/pipeline/loaders/events.py index ece7b25e..512c772d 100644 --- a/catalyst/pipeline/loaders/events.py +++ b/catalyst/pipeline/loaders/events.py @@ -231,7 +231,7 @@ class EventsLoader(PipelineLoader): self.load_next_events(n, dates, sids, mask), self.load_previous_events(p, dates, sids, mask), ) - + @property def columns(self): return self._columns diff --git a/catalyst/pipeline/loaders/frame.py b/catalyst/pipeline/loaders/frame.py index 827c12be..c663176e 100644 --- a/catalyst/pipeline/loaders/frame.py +++ b/catalyst/pipeline/loaders/frame.py @@ -180,4 +180,3 @@ class DataFrameLoader(PipelineLoader): @property def columns(self): return self._columns - diff --git a/catalyst/pipeline/loaders/synthetic.py b/catalyst/pipeline/loaders/synthetic.py index 1f88396a..fef8be24 100644 --- a/catalyst/pipeline/loaders/synthetic.py +++ b/catalyst/pipeline/loaders/synthetic.py @@ -163,7 +163,7 @@ class SeededRandomLoader(PrecomputedLoader): bool_dtype: self._bool_values, object_dtype: self._object_values, }[dtype](shape) - + @property def columns(self): return self._columns diff --git a/catalyst/support/issue_44.py b/catalyst/support/issue_44.py deleted file mode 100644 index b6d1277a..00000000 --- a/catalyst/support/issue_44.py +++ /dev/null @@ -1,109 +0,0 @@ -import pandas as pd -from catalyst import run_algorithm -from catalyst.exchange.exchange_utils import get_exchange_symbols - -from catalyst.api import ( - symbols, -) - - -def initialize(context): - context.i = -1 - context.base_currency = 'btc' - - -def handle_data(context, data): - lookback = 60 * 24 * 7 # (minutes, hours, days) - context.i += 1 - if context.i < lookback: - return - - today = context.blotter.current_dt.strftime('%Y-%m-%d %H:%M:%S') - - try: - # update universe everyday - new_day = 60 * 24 - if not context.i % new_day: - context.universe = universe(context, today) - - # get data every 30 minutes - minutes = 30 - if not context.i % minutes and context.universe: - for coin in context.coins: - pair = str(coin.symbol) - - # ohlcv data - open = data.history(coin, 'open', lookback, - '1m').ffill().bfill().resample( - '30T').first() - high = data.history(coin, 'high', lookback, - '1m').ffill().bfill().resample('30T').max() - low = data.history(coin, 'low', lookback, - '1m').ffill().bfill().resample('30T').min() - close = data.history(coin, 'price', lookback, - '1m').ffill().bfill().resample( - '30T').last() - volume = data.history(coin, 'volume', lookback, - '1m').ffill().bfill().resample( - '30T').sum() - - print(today, pair, close[-1]) - - except Exception as e: - print(e) - - -def analyze(context=None, results=None): - pass - - -def universe(context, today): - json_symbols = get_exchange_symbols('poloniex') - poloniex_universe_df = pd.DataFrame.from_dict( - json_symbols).transpose().astype(str) - poloniex_universe_df['base_currency'] = poloniex_universe_df.apply( - lambda row: row.symbol.split('_')[1], - axis=1) - poloniex_universe_df['market_currency'] = poloniex_universe_df.apply( - lambda row: row.symbol.split('_')[0], - axis=1) - poloniex_universe_df = poloniex_universe_df[ - poloniex_universe_df['base_currency'] == context.base_currency] - poloniex_universe_df = poloniex_universe_df[ - poloniex_universe_df.symbol != 'gas_btc'] - - # Markets currently not working on Catalyst 0.3.1 - # 2017-01-01 - # poloniex_universe_df = poloniex_universe_df[poloniex_universe_df.symbol != 'bcn_btc'] - # poloniex_universe_df = poloniex_universe_df[poloniex_universe_df.symbol != 'burst_btc'] - # poloniex_universe_df = poloniex_universe_df[poloniex_universe_df.symbol != 'dgb_btc'] - # poloniex_universe_df = poloniex_universe_df[poloniex_universe_df.symbol != 'doge_btc'] - # poloniex_universe_df = poloniex_universe_df[poloniex_universe_df.symbol != 'emc2_btc'] - # poloniex_universe_df = poloniex_universe_df[poloniex_universe_df.symbol != 'pink_btc'] - # poloniex_universe_df = poloniex_universe_df[poloniex_universe_df.symbol != 'sc_btc'] - print(poloniex_universe_df.head()) - - date = str(today).split(' ')[0] - - poloniex_universe_df = poloniex_universe_df[ - poloniex_universe_df.start_date < date] - context.coins = symbols(*poloniex_universe_df.symbol) - print(len(poloniex_universe_df)) - return poloniex_universe_df.symbol.tolist() - - -if __name__ == '__main__': - start_date = pd.to_datetime('2017-01-01', utc=True) - end_date = pd.to_datetime('2017-10-15', utc=True) - - performance = run_algorithm(start=start_date, end=end_date, - capital_base=10000.0, - initialize=initialize, - handle_data=handle_data, - analyze=analyze, - exchange_name='poloniex', - data_frequency='minute', - base_currency='btc', - live=False, - live_graph=False, - algo_namespace='test') diff --git a/catalyst/support/issue_47.py b/catalyst/support/issue_47.py deleted file mode 100644 index 3ebc0bc7..00000000 --- a/catalyst/support/issue_47.py +++ /dev/null @@ -1,139 +0,0 @@ -""" -Requires Catalyst version 0.3.0 or above -Tested on Catalyst version 0.3.3 - -These example aims to provide and easy way for users to learn how to collect data from the different exchanges. -You simply need to specify the exchange and the market that you want to focus on. -You will all see how to create a universe and filter it base on the exchange and the market you desire. - -The example prints out the closing price of all the pairs for a given market-exchange every 30 minutes. -The example also contains the ohlcv minute data for the past seven days which could be used to create indicators -Use this as the backbone to create your own trading strategies. - -Variables lookback date and date are used to ensure data for a coin existed on the lookback period specified. -""" - -import numpy as np -import pandas as pd -from datetime import timedelta -from catalyst import run_algorithm -from catalyst.exchange.exchange_utils import get_exchange_symbols - -from catalyst.api import ( - symbols, -) - - -def initialize(context): - context.i = -1 # counts the minutes - context.exchange = 'poloniex' # must match the exchange specified in run_algorithm - context.base_currency = 'btc' # must match the base currency specified in run_algorithm - - -def handle_data(context, data): - lookback = 60 * 24 * 7 # (minutes, hours, days) of how far to lookback in the data history - context.i += 1 - - # current date formatted into a string - today = context.blotter.current_dt - date, time = today.strftime('%Y-%m-%d %H:%M:%S').split(' ') - lookback_date = today - timedelta(days=( - lookback / (60 * 24))) # subtract the amount of days specified in lookback - lookback_date = lookback_date.strftime('%Y-%m-%d %H:%M:%S').split(' ')[ - 0] # get only the date as a string - - # update universe everyday - new_day = 60 * 24 - if not context.i % new_day: - context.universe = universe(context, lookback_date, date) - - # get data every 30 minutes - minutes = 30 - if not context.i % minutes and context.universe: - # we iterate for every pair in the current universe - for coin in context.coins: - pair = str(coin.symbol) - - # 30 minute interval ohlcv data (the standard data required for candlestick or indicators/signals) - # 30T means 30 minutes re-sampling of one minute data. change to your desire time interval. - opened = fill(data.history(coin, 'open', bar_count=lookback, - frequency='30T')).values - high = fill(data.history(coin, 'high', bar_count=lookback, - frequency='30T')).values - low = fill(data.history(coin, 'low', bar_count=lookback, - frequency='30T')).values - close = fill(data.history(coin, 'price', bar_count=lookback, - frequency='30T')).values - volume = fill(data.history(coin, 'volume', bar_count=lookback, - frequency='30T')).values - - # close[-1] is the equivalent to current price - # displays the minute price for each pair every 30 minutes - print( - today, pair, opened[-1], high[-1], low[-1], close[-1], volume[-1]) - - # ---------------------------------------------------------------------------------------------------------- - # -------------------------------------- Insert Your Strategy Here ----------------------------------------- - # ---------------------------------------------------------------------------------------------------------- - - -def analyze(context=None, results=None): - pass - - -# Get the universe for a given exchange and a given base_currency market -# Example: Poloniex btc Market -def universe(context, lookback_date, current_date): - json_symbols = get_exchange_symbols( - context.exchange) # get all the pairs for the exchange - universe_df = pd.DataFrame.from_dict(json_symbols).transpose().astype( - str) # convert into a dataframe - universe_df['base_currency'] = universe_df.apply( - lambda row: row.symbol.split('_')[1], - axis=1) - universe_df['market_currency'] = universe_df.apply( - lambda row: row.symbol.split('_')[0], - axis=1) - # Filter all the exchange pairs to only the ones for a give base currency - universe_df = universe_df[ - universe_df['base_currency'] == context.base_currency] - - # Filter all the pairs to ensure that pair existed in the current date range - universe_df = universe_df[universe_df.start_date < lookback_date] - universe_df = universe_df[universe_df.end_daily >= current_date] - context.coins = symbols( - *universe_df.symbol) # convert all the pairs to symbols - return universe_df.symbol.tolist() - - -# Replace all NA, NAN or infinite values with its nearest value -def fill(series): - if isinstance(series, pd.Series): - return series.replace([np.inf, -np.inf], np.nan).ffill().bfill() - elif isinstance(series, np.ndarray): - return pd.Series(series).replace([np.inf, -np.inf], - np.nan).ffill().bfill().values - else: - return series - - -if __name__ == '__main__': - start_date = pd.to_datetime('2017-01-08', utc=True) - end_date = pd.to_datetime('2017-11-13', utc=True) - - performance = run_algorithm(start=start_date, end=end_date, - capital_base=10000.0, - initialize=initialize, - handle_data=handle_data, - analyze=analyze, - exchange_name='poloniex', - data_frequency='minute', - base_currency='btc', - live=False, - live_graph=False, - algo_namespace='simple_universe') - -""" -Run in Terminal (inside catalyst environment): -python simple_universe.py -""" diff --git a/catalyst/support/issue_55.py b/catalyst/support/issue_55.py index 195f5019..733a1585 100644 --- a/catalyst/support/issue_55.py +++ b/catalyst/support/issue_55.py @@ -1,4 +1,3 @@ -import talib import pandas as pd from catalyst import run_algorithm diff --git a/catalyst/support/issue_57.py b/catalyst/support/issue_57.py deleted file mode 100644 index f7bfcd18..00000000 --- a/catalyst/support/issue_57.py +++ /dev/null @@ -1,46 +0,0 @@ -import talib -import pandas as pd - -from catalyst import run_algorithm -from catalyst.api import symbol - - -def initialize(context): - print('initializing') - context.asset = symbol('btc_usdt') - - -def handle_data(context, data): - print('handling bar: {}'.format(data.current_dt)) - - price = data.current(context.asset, 'close') - print('got price {price}'.format(price=price)) - - try: - prices = data.history( - context.asset, - fields='close', - bar_count=60, - frequency='1D' - ) - print('got {} price entries\n'.format(len(prices), prices)) - except Exception as e: - print(e) - - -run_algorithm( - capital_base=1, - start=pd.to_datetime('2016-2-11', utc=True), - end=pd.to_datetime('2017-8-31', utc=True), - data_frequency='daily', - initialize=initialize, - handle_data=handle_data, - analyze=None, - exchange_name='bittrex', - algo_namespace='issue_57', - base_currency='btc' -<<<<<<< HEAD -) -======= -) ->>>>>>> develop diff --git a/catalyst/support/issue_74.py b/catalyst/support/issue_74.py deleted file mode 100644 index ad6d6fee..00000000 --- a/catalyst/support/issue_74.py +++ /dev/null @@ -1,127 +0,0 @@ -from __future__ import division -import os -import pytz -import numpy as np -import pandas as pd -from scipy.optimize import minimize -import matplotlib.pyplot as plt -from datetime import datetime - -from catalyst.api import record, symbol, symbols, order_target_percent -from catalyst.utils.run_algo import run_algorithm - -np.set_printoptions(threshold='nan', suppress=True) - - -def initialize(context): - # Portfolio assets list - context.assets = symbols('btc_usdt', 'eth_usdt', 'ltc_usdt', 'dash_usdt', - 'xmr_usdt') - context.nassets = len(context.assets) - # Set the time window that will be used to compute expected return - # and asset correlations - context.window = 180 - # Set the number of days between each portfolio rebalancing - context.rebalance_period = 30 - context.i = 0 - - -def handle_data(context, data): - # Only rebalance at the beggining of the algorithm execution and - # every multiple of the rebalance period - if context.i == 0 or context.i % context.rebalance_period == 0: - n = context.window - prices = data.history(context.assets, fields='price', - bar_count=n + 1, frequency='daily') - pr = np.asmatrix(prices) - t_prices = prices.iloc[1:n + 1] - t_val = t_prices.values - tminus_prices = prices.iloc[0:n] - tminus_val = tminus_prices.values - # Compute daily returns (r) - r = np.asmatrix(t_val / tminus_val - 1) - # Compute the expected returns of each asset with the average - # daily return for the selected time window - m = np.asmatrix(np.mean(r, axis=0)) - # ### - stds = np.std(r, axis=0) - # Compute excess returns matrix (xr) - xr = r - m - # Matrix algebra to get variance-covariance matrix - cov_m = np.dot(np.transpose(xr), xr) / n - # Compute asset correlation matrix (informative only) - corr_m = cov_m / np.dot(np.transpose(stds), stds) - - # Define portfolio optimization parameters - n_portfolios = 50000 - results_array = np.zeros((3 + context.nassets, n_portfolios)) - for p in xrange(n_portfolios): - weights = np.random.random(context.nassets) - weights /= np.sum(weights) - w = np.asmatrix(weights) - p_r = np.sum(np.dot(w, np.transpose(m))) * 365 - p_std = np.sqrt( - np.dot(np.dot(w, cov_m), np.transpose(w))) * np.sqrt(365) - - # store results in results array - results_array[0, p] = p_r - results_array[1, p] = p_std - # store Sharpe Ratio (return / volatility) - risk free rate element - # excluded for simplicity - results_array[2, p] = results_array[0, p] / results_array[1, p] - i = 0 - for iw in weights: - results_array[3 + i, p] = weights[i] - i += 1 - - # convert results array to Pandas DataFrame - results_frame = pd.DataFrame(np.transpose(results_array), - columns=['r', 'stdev', - 'sharpe'] + context.assets) - # locate position of portfolio with highest Sharpe Ratio - max_sharpe_port = results_frame.iloc[results_frame['sharpe'].idxmax()] - # locate positon of portfolio with minimum standard deviation - min_vol_port = results_frame.iloc[results_frame['stdev'].idxmin()] - - # order optimal weights for each asset - for asset in context.assets: - if data.can_trade(asset): - order_target_percent(asset, max_sharpe_port[asset]) - - # create scatter plot coloured by Sharpe Ratio - plt.scatter(results_frame.stdev, results_frame.r, - c=results_frame.sharpe, cmap='RdYlGn') - plt.xlabel('Volatility') - plt.ylabel('Returns') - plt.colorbar() - # plot red star to highlight position of portfolio with highest Sharpe Ratio - plt.scatter(max_sharpe_port[1], max_sharpe_port[0], marker='o', - color='b', s=200) - # plot green star to highlight position of minimum variance portfolio - plt.show() - print(max_sharpe_port) - record(pr=pr, r=r, m=m, stds=stds, max_sharpe_port=max_sharpe_port, - corr_m=corr_m) - context.i += 1 - - -def analyze(context=None, results=None): - # Form DataFrame with selected data - data = results[['pr', 'r', 'm', 'stds', 'max_sharpe_port', 'corr_m', - 'portfolio_value']] - - # Save results in CSV file - filename = os.path.splitext(os.path.basename(__file__))[0] - data.to_csv(filename + '.csv') - - -# Bitcoin data is available from 2015-3-2. Dates vary for other tokens. -start = datetime(2017, 1, 1, 0, 0, 0, 0, pytz.utc) -end = datetime(2017, 8, 16, 0, 0, 0, 0, pytz.utc) -results = run_algorithm(initialize=initialize, - handle_data=handle_data, - analyze=analyze, - start=start, - end=end, - exchange_name='poloniex', - capital_base=100000, ) diff --git a/catalyst/support/rodrigo_1.py b/catalyst/support/rodrigo_1.py deleted file mode 100644 index ec32c617..00000000 --- a/catalyst/support/rodrigo_1.py +++ /dev/null @@ -1,153 +0,0 @@ -import pandas as pd -from logbook import Logger, DEBUG - -from catalyst import run_algorithm -from catalyst.api import (schedule_function, order_target_percent, symbol, - date_rules, get_open_orders, cancel_order, record, - set_commission, set_slippage) - -log = Logger('rodrigo_1', level=DEBUG) -""" -The initialize function sets any data or variables that -you'll use in your algorithm. -It's only called once at the beginning of your algorithm. -""" - - -def initialize(context): - # Select asset of interest - context.asset = symbol('BTC_USD') - - # set_commission(TradingPairFeeSchedule(maker_fee=0.5, taker_fee=0.5)) - # set_slippage(TradingPairFixedSlippage(spread=0.5)) - # Set up a rebalance method to run every day - schedule_function(rebalance, date_rule=date_rules.every_day()) - - -""" -Rebalance function scheduled to run once per day. -""" - - -def rebalance(context, data): - # To make market decisions, we're calculating the token's - # moving average for the last 5 days. - - # We get the price history for the last 5 days. - price_history = data.history(context.asset, fields='price', bar_count=5, - frequency='1d') - - # Then we take an average of those 5 days. - average_price = price_history.mean() - - # We also get the coin's current price. - price = data.current(context.asset, 'price') - - # Cancel any outstanding orders - orders = get_open_orders(context.asset) or [] - for order in orders: - cancel_order(order) - - # If our coin is currently listed on a major exchange - if data.can_trade(context.asset): - # If the current price is 1% above the 5-day average price, - # we open a long position. If the current price is below the - # average price, then we want to close our position to 0 shares. - if price > (1.01 * average_price): - # Place the buy order (positive means buy, negative means sell) - order_target_percent(context.asset, .99) - log.info("Buying %s" % (context.asset.symbol)) - elif price < average_price: - # Sell all of our shares by setting the target position to zero - order_target_percent(context.asset, 0) - log.info("Selling %s" % (context.asset.symbol)) - - # Use the record() method to track up to five custom signals. - # Record Apple's current price and the average price over the last - # five days. - cash = context.portfolio.cash - leverage = context.account.leverage - - record(price=price, average_price=average_price, cash=cash, - leverage=leverage) - - -def analyze(context=None, results=None): - import matplotlib.pyplot as plt - - # Plot the portfolio and asset data. - ax1 = plt.subplot(511) - results[['portfolio_value']].plot(ax=ax1) - ax1.set_ylabel('Portfolio Value (USD)') - - ax2 = plt.subplot(512, sharex=ax1) - ax2.set_ylabel('{asset} (USD)'.format(asset=context.asset)) - (results[[ - 'price', - ]]).plot(ax=ax2) - - trans = results.ix[[t != [] for t in results.transactions]] - buys = trans.ix[ - [t[0]['amount'] > 0 for t in trans.transactions] - ] - sells = trans.ix[ - [t[0]['amount'] < 0 for t in trans.transactions] - ] - - ax2.plot( - buys.index, - results.price[buys.index], - '^', - markersize=10, - color='g', - ) - ax2.plot( - sells.index, - results.price[sells.index], - 'v', - markersize=10, - color='r', - ) - - ax3 = plt.subplot(513, sharex=ax1) - results[['leverage']].plot(ax=ax3) - ax3.set_ylabel('Leverage ') - - ax4 = plt.subplot(514, sharex=ax1) - results[['cash']].plot(ax=ax4) - ax4.set_ylabel('Cash (USD)') - - results[[ - 'algorithm', - 'benchmark', - ]] = results[[ - 'algorithm_period_return', - 'benchmark_period_return', - ]] - - ax5 = plt.subplot(515, sharex=ax1) - results[[ - 'algorithm', - 'benchmark', - ]].plot(ax=ax5) - ax5.set_ylabel('Percent Change') - - plt.legend(loc=3) - - # Show the plot. - plt.gcf().set_size_inches(18, 8) - plt.show() - - -run_algorithm( - capital_base=100000, - start=pd.to_datetime('2017-1-1', utc=True), - end=pd.to_datetime('2017-10-22', utc=True), - data_frequency='minute', - initialize=initialize, - handle_data=None, - analyze=analyze, - exchange_name='bitfinex', - algo_namespace='rodrigo_1', - base_currency='usd' -) diff --git a/catalyst/utils/calendars/exchange_calendar_open.py b/catalyst/utils/calendars/exchange_calendar_open.py index 54be460f..f7be8e22 100644 --- a/catalyst/utils/calendars/exchange_calendar_open.py +++ b/catalyst/utils/calendars/exchange_calendar_open.py @@ -31,4 +31,5 @@ class OpenExchangeCalendar(TradingCalendar): return DateOffset(days=1) def __init__(self, *args, **kwargs): - super(OpenExchangeCalendar, self).__init__(start=Timestamp('2015-3-1', tz='UTC'), **kwargs) + super(OpenExchangeCalendar, self).__init__( + start=Timestamp('2015-3-1', tz='UTC'), **kwargs) diff --git a/catalyst/utils/cli.py b/catalyst/utils/cli.py index 51e519e0..59a7300a 100644 --- a/catalyst/utils/cli.py +++ b/catalyst/utils/cli.py @@ -9,6 +9,7 @@ DEFAULT_BAR_TEMPLATE = ' [%(bar)s] %(label)s: %(info)s' DEFAULT_EMPTY_CHAR = ' ' DEFAULT_FILL_CHAR = '=' + def item_show_count(total=None): def maybe_show_total(index): if total is not None: @@ -17,12 +18,13 @@ def item_show_count(total=None): def item_show_func(item, _it=iter(count())): if item is not None: - starting = False + # starting = False return maybe_show_total(next(_it)) return 'DONE' return item_show_func + def maybe_show_progress(it, show_progress, empty_char=DEFAULT_EMPTY_CHAR, diff --git a/catalyst/utils/math_utils.py b/catalyst/utils/math_utils.py index 7981a52b..8e3e086c 100644 --- a/catalyst/utils/math_utils.py +++ b/catalyst/utils/math_utils.py @@ -17,9 +17,11 @@ import math from numpy import isnan + def round_nearest(x, a): return round(round(x / a) * a, -int(math.floor(math.log10(a)))) + def tolerant_equals(a, b, atol=10e-7, rtol=10e-7, equal_nan=False): """Check if a and b are equal with some tolerance. diff --git a/catalyst/utils/paths.py b/catalyst/utils/paths.py index 8ec87c7e..2764b623 100644 --- a/catalyst/utils/paths.py +++ b/catalyst/utils/paths.py @@ -126,7 +126,7 @@ def catalyst_root(environ=None): root = environ.get('ZIPLINE_ROOT', None) if root is None: - root = os.path.join(expanduser('~'),'.catalyst') + root = os.path.join(expanduser('~'), '.catalyst') return root diff --git a/catalyst/utils/run_algo.py b/catalyst/utils/run_algo.py index 92a5e335..3c62ea2d 100644 --- a/catalyst/utils/run_algo.py +++ b/catalyst/utils/run_algo.py @@ -8,6 +8,7 @@ from time import sleep import click import pandas as pd +from logbook import Logger from catalyst.data.bundles import load from catalyst.data.data_portal import DataPortal @@ -30,17 +31,16 @@ from catalyst.utils.factory import create_simulation_parameters from catalyst.data.loader import load_crypto_market_data import catalyst.utils.paths as pth -from catalyst.exchange.exchange_algorithm import ExchangeTradingAlgorithmLive, \ - ExchangeTradingAlgorithmBacktest +from catalyst.exchange.exchange_algorithm import ( + ExchangeTradingAlgorithmLive, + ExchangeTradingAlgorithmBacktest, +) from catalyst.exchange.exchange_data_portal import DataPortalExchangeLive, \ DataPortalExchangeBacktest from catalyst.exchange.asset_finder_exchange import AssetFinderExchange -from catalyst.exchange.exchange_portfolio import ExchangePortfolio from catalyst.exchange.exchange_errors import ( ExchangeRequestError, ExchangeRequestErrorTooManyAttempts, BaseCurrencyNotFoundError) -from catalyst.exchange.exchange_utils import get_algo_object -from logbook import Logger from catalyst.constants import LOG_LEVEL @@ -172,7 +172,7 @@ def _run(handle_data, asset_db_path=None # We don't need an asset db, we have exchanges ) env.asset_finder = AssetFinderExchange() - choose_loader = None # TODO: use the DataPortal for in the algorithm class for this + choose_loader = None # TODO: use the DataPortal in the algo class for this if live: start = pd.Timestamp.utcnow() diff --git a/tests/exchange/test_bcolz.py b/tests/exchange/test_bcolz.py index 8c76799a..a842bee7 100644 --- a/tests/exchange/test_bcolz.py +++ b/tests/exchange/test_bcolz.py @@ -116,7 +116,7 @@ class TestBcolzWriter(object): df = self.generate_df(exchange_name, freq, start, end) - print df.index[0],df.index[-1] + print(df.index[0], df.index[-1]) writer = BcolzExchangeBarWriter( rootdir=self.root_dir, @@ -140,7 +140,7 @@ class TestBcolzWriter(object): dx = get_df_from_arrays(arrays, periods) - assert_equals(df.equals(df), True) + assert_equals(df.equals(dx), True) pass def test_bcolz_bitfinex_daily_write_read(self): diff --git a/tests/exchange/test_bitfinex.py b/tests/exchange/test_bitfinex.py index 194422f5..1859f38f 100644 --- a/tests/exchange/test_bitfinex.py +++ b/tests/exchange/test_bitfinex.py @@ -34,7 +34,7 @@ class TestBitfinex(BaseExchangeTestCase): def test_open_orders(self): log.info('retrieving open orders') - orders = self.exchange.get_open_orders() + # orders = self.exchange.get_open_orders() pass def test_get_order(self): @@ -47,18 +47,17 @@ class TestBitfinex(BaseExchangeTestCase): def test_get_candles(self): log.info('retrieving candles') - ohlcv_neo = self.exchange.get_candles( - freq='1T', - assets=self.exchange.get_asset('neo_btc') - ) + # ohlcv_neo = self.exchange.get_candles( + # freq='1T', + # assets=self.exchange.get_asset('neo_btc')) pass def test_tickers(self): log.info('retrieving tickers') - tickers = self.exchange.tickers([ - self.exchange.get_asset('eth_btc'), - self.exchange.get_asset('etc_btc') - ]) + # tickers = self.exchange.tickers([ + # self.exchange.get_asset('eth_btc'), + # self.exchange.get_asset('etc_btc') + # ]) pass def test_get_account(self): @@ -67,11 +66,11 @@ class TestBitfinex(BaseExchangeTestCase): def test_get_balances(self): log.info('testing exchange balances') - balances = self.exchange.get_balances() + # balances = self.exchange.get_balances() pass def test_orderbook(self): log.info('testing order book for bitfinex') - asset = self.exchange.get_asset('eth_btc') - orderbook = self.exchange.get_orderbook(asset) + # asset = self.exchange.get_asset('eth_btc') + # orderbook = self.exchange.get_orderbook(asset) pass diff --git a/tests/exchange/test_bittrex.py b/tests/exchange/test_bittrex.py index bf17970d..47d54068 100644 --- a/tests/exchange/test_bittrex.py +++ b/tests/exchange/test_bittrex.py @@ -1,4 +1,4 @@ -import pandas as pd +# import pandas as pd from catalyst.exchange.bittrex.bittrex import Bittrex from catalyst.finance.order import Order from base import BaseExchangeTestCase @@ -33,8 +33,8 @@ class TestBittrex(BaseExchangeTestCase): def test_open_orders(self): log.info('retrieving open orders') - asset = self.exchange.get_asset('neo_btc') - orders = self.exchange.get_open_orders(asset) + # asset = self.exchange.get_asset('neo_btc') + # orders = self.exchange.get_open_orders(asset) pass def test_get_order(self): @@ -51,21 +51,21 @@ class TestBittrex(BaseExchangeTestCase): def test_get_candles(self): log.info('retrieving candles') - ohlcv_neo = self.exchange.get_candles( - freq='5T', - assets=self.exchange.get_asset('neo_btc'), - bar_count=20, - end_dt=pd.to_datetime('2017-10-20', utc=True) - ) - ohlcv_neo_ubq = self.exchange.get_candles( - freq='1D', - assets=[ - self.exchange.get_asset('neo_btc'), - self.exchange.get_asset('ubq_btc') - ], - bar_count=14, - end_dt=pd.to_datetime('2017-10-20', utc=True) - ) + # ohlcv_neo = self.exchange.get_candles( + # freq='5T', + # assets=self.exchange.get_asset('neo_btc'), + # bar_count=20, + # end_dt=pd.to_datetime('2017-10-20', utc=True) + # ) + # ohlcv_neo_ubq = self.exchange.get_candles( + # freq='1D', + # assets=[ + # self.exchange.get_asset('neo_btc'), + # self.exchange.get_asset('ubq_btc') + # ], + # bar_count=14, + # end_dt=pd.to_datetime('2017-10-20', utc=True) + # ) pass def test_tickers(self): @@ -79,7 +79,7 @@ class TestBittrex(BaseExchangeTestCase): def test_get_balances(self): log.info('testing wallet balances') - balances = self.exchange.get_balances() + # balances = self.exchange.get_balances() pass def test_get_account(self): @@ -88,6 +88,6 @@ class TestBittrex(BaseExchangeTestCase): def test_orderbook(self): log.info('testing order book for bittrex') - asset = self.exchange.get_asset('eth_btc') - orderbook = self.exchange.get_orderbook(asset) + # asset = self.exchange.get_asset('eth_btc') + # orderbook = self.exchange.get_orderbook(asset) pass diff --git a/tests/exchange/test_bundle.py b/tests/exchange/test_bundle.py index 89ef3062..a0d23319 100644 --- a/tests/exchange/test_bundle.py +++ b/tests/exchange/test_bundle.py @@ -1,11 +1,10 @@ -import hashlib +# import hashlib import os import tempfile from logging import getLogger import pandas as pd -from catalyst import get_calendar from catalyst.exchange.bundle_utils import get_bcolz_chunk, \ get_start_dt, get_df_from_arrays from catalyst.exchange.exchange_bcolz import BcolzExchangeBarReader, \ @@ -22,22 +21,22 @@ log = getLogger('test_exchange_bundle') class TestExchangeBundle: def test_spot_value(self): - data_frequency = 'daily' - exchange_name = 'poloniex' + # data_frequency = 'daily' + # exchange_name = 'poloniex' - exchange = get_exchange(exchange_name) - exchange_bundle = ExchangeBundle(exchange) - assets = [ - exchange.get_asset('btc_usdt') - ] - dt = pd.to_datetime('2017-10-14', utc=True) + # exchange = get_exchange(exchange_name) + # exchange_bundle = ExchangeBundle(exchange) + # assets = [ + # exchange.get_asset('btc_usdt') + # ] + # dt = pd.to_datetime('2017-10-14', utc=True) - values = exchange_bundle.get_spot_values( - assets=assets, - field='close', - dt=dt, - data_frequency=data_frequency - ) + # values = exchange_bundle.get_spot_values( + # assets=assets, + # field='close', + # dt=dt, + # data_frequency=data_frequency + # ) pass def test_ingest_minute(self): @@ -215,7 +214,7 @@ class TestExchangeBundle: # encounter these problems as I have been focusing on minute data. reader = exchange_bundle.get_reader(data_frequency) for asset in assets: - # Since this pair was loaded last. It should be there in daily mode. + # Since this pair was loaded last. It should be here in daily mode. arrays = reader.load_raw_arrays( sids=[asset.sid], fields=['close'], @@ -252,7 +251,6 @@ class TestExchangeBundle: ensure_directory(path) exchange_bundle = ExchangeBundle(exchange) - calendar = get_calendar('OPEN') # We are using a BcolzMinuteBarWriter even though the data is daily # Each day has a maximum of one bar @@ -304,26 +302,25 @@ class TestExchangeBundle: pass def test_minute_bundle(self): - exchange_name = 'poloniex' - data_frequency = 'minute' + # exchange_name = 'poloniex' + # data_frequency = 'minute' - exchange = get_exchange(exchange_name) - asset = exchange.get_asset('neos_btc') - - path = get_bcolz_chunk( - exchange_name=exchange_name, - symbol=asset.symbol, - data_frequency=data_frequency, - period='2017-5', - ) + # exchange = get_exchange(exchange_name) + # asset = exchange.get_asset('neos_btc') + # path = get_bcolz_chunk( + # exchange_name=exchange_name, + # symbol=asset.symbol, + # data_frequency=data_frequency, + # period='2017-5', + # ) pass def test_hash_symbol(self): - symbol = 'etc_btc' - sid = int( - hashlib.sha256(symbol.encode('utf-8')).hexdigest(), 16 - ) % 10 ** 6 + # symbol = 'etc_btc' + # sid = int( + # hashlib.sha256(symbol.encode('utf-8')).hexdigest(), 16 + # ) % 10 ** 6 pass def test_validate_data(self): diff --git a/tests/exchange/test_ccxt.py b/tests/exchange/test_ccxt.py index b1df6a19..3ff44a8b 100644 --- a/tests/exchange/test_ccxt.py +++ b/tests/exchange/test_ccxt.py @@ -1,13 +1,10 @@ -import os -import tempfile - import pandas as pd +from logbook import Logger +from base import BaseExchangeTestCase + from catalyst.exchange.ccxt.ccxt_exchange import CCXT from catalyst.finance.order import Order -from base import BaseExchangeTestCase -from logbook import Logger from catalyst.exchange.exchange_utils import get_exchange_auth -from catalyst.utils.paths import ensure_directory log = Logger('test_ccxt') @@ -38,9 +35,9 @@ class TestCCXT(BaseExchangeTestCase): pass def test_open_orders(self): - log.info('retrieving open orders') - asset = self.exchange.get_asset('neo_eth') - orders = self.exchange.get_open_orders(asset) + # log.info('retrieving open orders') + # asset = self.exchange.get_asset('neo_eth') + # orders = self.exchange.get_open_orders(asset) pass def test_get_order(self): @@ -79,7 +76,7 @@ class TestCCXT(BaseExchangeTestCase): def test_get_balances(self): log.info('testing wallet balances') - balances = self.exchange.get_balances() + # balances = self.exchange.get_balances() pass def test_get_account(self): @@ -88,8 +85,8 @@ class TestCCXT(BaseExchangeTestCase): def test_orderbook(self): log.info('testing order book for bittrex') - asset = self.exchange.get_asset('eth_btc') - orderbook = self.exchange.get_orderbook(asset, 'all', limit=10) + # asset = self.exchange.get_asset('eth_btc') + # orderbook = self.exchange.get_orderbook(asset, 'all', limit=10) pass def test_get_fees(self): diff --git a/tests/exchange/test_data_portal.py b/tests/exchange/test_data_portal.py index 7b2b4720..29ef4d46 100644 --- a/tests/exchange/test_data_portal.py +++ b/tests/exchange/test_data_portal.py @@ -3,11 +3,13 @@ from logbook import Logger from catalyst import get_calendar from catalyst.exchange.asset_finder_exchange import AssetFinderExchange -from catalyst.exchange.exchange_data_portal import DataPortalExchangeBacktest, \ +from catalyst.exchange.exchange_data_portal import ( + DataPortalExchangeBacktest, DataPortalExchangeLive +) from catalyst.exchange.exchange_utils import get_common_assets -from catalyst.exchange.factory import get_exchange, get_exchanges -from test_utils import rnd_history_date_days, rnd_bar_count, output_df +from catalyst.exchange.factory import get_exchanges +from test_utils import rnd_history_date_days, rnd_bar_count log = Logger('test_bitfinex') @@ -35,31 +37,31 @@ class TestExchangeDataPortal: ) def test_get_history_window_live(self): - asset_finder = self.data_portal_live.asset_finder + # asset_finder = self.data_portal_live.asset_finder - assets = [ - asset_finder.lookup_symbol('eth_btc', self.bitfinex), - asset_finder.lookup_symbol('eth_btc', self.bittrex) - ] - now = pd.Timestamp.utcnow() - data = self.data_portal_live.get_history_window( - assets, - now, - 10, - '1m', - 'price') + # assets = [ + # asset_finder.lookup_symbol('eth_btc', self.bitfinex), + # asset_finder.lookup_symbol('eth_btc', self.bittrex) + # ] + # now = pd.Timestamp.utcnow() + # data = self.data_portal_live.get_history_window( + # assets, + # now, + # 10, + # '1m', + # 'price') pass def test_get_spot_value_live(self): - asset_finder = self.data_portal_live.asset_finder + # asset_finder = self.data_portal_live.asset_finder - assets = [ - asset_finder.lookup_symbol('eth_btc', self.bitfinex), - asset_finder.lookup_symbol('eth_btc', self.bittrex) - ] - now = pd.Timestamp.utcnow() - value = self.data_portal_live.get_spot_value( - assets, 'price', now, '1m') + # assets = [ + # asset_finder.lookup_symbol('eth_btc', self.bitfinex), + # asset_finder.lookup_symbol('eth_btc', self.bittrex) + # ] + # now = pd.Timestamp.utcnow() + # value = self.data_portal_live.get_spot_value( + # assets, 'price', now, '1m') pass def test_get_history_window_backtest(self): diff --git a/tests/exchange/test_poloniex.py b/tests/exchange/test_poloniex.py index d84d1010..62800468 100644 --- a/tests/exchange/test_poloniex.py +++ b/tests/exchange/test_poloniex.py @@ -34,8 +34,8 @@ class TestPoloniex(BaseExchangeTestCase): def test_open_orders(self): log.info('retrieving open orders') - asset = self.exchange.get_asset('neos_btc') - orders = self.exchange.get_open_orders(asset) + # asset = self.exchange.get_asset('neos_btc') + # orders = self.exchange.get_open_orders(asset) pass def test_get_order(self): @@ -79,7 +79,7 @@ class TestPoloniex(BaseExchangeTestCase): def test_get_balances(self): log.info('testing wallet balances') - balances = self.exchange.get_balances() + # balances = self.exchange.get_balances() pass def test_get_account(self): @@ -88,7 +88,6 @@ class TestPoloniex(BaseExchangeTestCase): def test_orderbook(self): log.info('testing order book for poloniex') - asset = self.exchange.get_asset('eth_btc') - - orderbook = self.exchange.get_orderbook(asset) + # asset = self.exchange.get_asset('eth_btc') + # orderbook = self.exchange.get_orderbook(asset) pass diff --git a/tests/exchange/test_server_bundle.py b/tests/exchange/test_server_bundle.py index 42b2d3f3..ea90c0f2 100644 --- a/tests/exchange/test_server_bundle.py +++ b/tests/exchange/test_server_bundle.py @@ -1,21 +1,16 @@ import os -import tarfile import importlib + import pandas as pd - -from catalyst import get_calendar - -from catalyst.exchange.exchange_bundle import ExchangeBundle -from catalyst.exchange.exchange_bcolz import BcolzExchangeBarReader -from catalyst.data.minute_bars import BcolzMinuteBarMetadata -from catalyst.exchange.bundle_utils import get_df_from_arrays, get_bcolz_chunk - import matplotlib import matplotlib.pyplot as plt from matplotlib.finance import candlestick2_ohlc -from matplotlib.finance import volume_overlay +# from matplotlib.finance import volume_overlay import matplotlib.ticker as ticker +from catalyst.exchange.exchange_bundle import ExchangeBundle +from catalyst.exchange.exchange_bcolz import BcolzExchangeBarReader +from catalyst.exchange.bundle_utils import get_df_from_arrays, get_bcolz_chunk from catalyst.exchange.factory import get_exchange EXCHANGE_NAMES = ['bitfinex', 'bittrex', 'poloniex'] @@ -85,8 +80,8 @@ class ValidateChunks(object): matplotlib.transforms.Bbox([[0.125, 0.1], [0.9, 0.26]])) # Plot the volume overlay - bc = volume_overlay(ax2, df['open'], df['close'], df['volume'], - colorup='g', alpha=0.5, width=1) + # bc = volume_overlay(ax2, df['open'], df['close'], df['volume'], + # colorup='g', alpha=0.5, width=1) ax.xaxis.set_major_locator(ticker.MaxNLocator(6)) diff --git a/tests/exchange/test_utils.py b/tests/exchange/test_utils.py index ddf76215..17a774fd 100644 --- a/tests/exchange/test_utils.py +++ b/tests/exchange/test_utils.py @@ -26,8 +26,7 @@ def rnd_history_date_minutes(max_minutes=1440): def rnd_bar_count(max_bars=21): - now = pd.Timestamp.utcnow() - + # now = pd.Timestamp.utcnow() return randint(0, max_bars) From 313db1def961fb5a97892730c4d68a11e2774ede Mon Sep 17 00:00:00 2001 From: fredfortier Date: Fri, 8 Dec 2017 15:22:53 -0500 Subject: [PATCH 30/52] BLD: improvements to stats output --- catalyst/exchange/exchange_algorithm.py | 1 + catalyst/exchange/stats_utils.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index 2ce0357f..5fd59188 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -671,6 +671,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): except Exception as e: log.warn('unable to calculate performance: {}'.format(e)) + # Writing the stats output csv_bytes = None try: csv_bytes = stats_to_algo_folder( diff --git a/catalyst/exchange/stats_utils.py b/catalyst/exchange/stats_utils.py index b5a080dc..737ad608 100644 --- a/catalyst/exchange/stats_utils.py +++ b/catalyst/exchange/stats_utils.py @@ -336,6 +336,20 @@ def stats_to_s3(uri, stats, algo_namespace, recorded_cols=None, def stats_to_algo_folder(stats, algo_namespace, recorded_cols=None): + """ + Saves the performance stats to the algo local folder. + + Parameters + ---------- + stats: list[Object] + algo_namespace: str + recorded_cols: list[str] + + Returns + ------- + str + + """ bytes_to_write = get_csv_stats(stats, recorded_cols=recorded_cols) timestr = time.strftime('%Y%m%d') From 4c15f5efda808b4ba7677936e70f2c8a1b5bdd25 Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Fri, 8 Dec 2017 14:01:06 -0700 Subject: [PATCH 31/52] BUG: _run() missing paper-trading params --- catalyst/__main__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/catalyst/__main__.py b/catalyst/__main__.py index 47c7dc55..9e6787b5 100644 --- a/catalyst/__main__.py +++ b/catalyst/__main__.py @@ -283,7 +283,9 @@ def run(ctx, exchange=exchange_name, algo_namespace=algo_namespace, base_currency=base_currency, - live_graph=False + live_graph=False, + simulate_orders=True, + stats_output=None, ) if output == '-': From ebd1ca44f623a1e68c702439a7e252593a4c7074 Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Fri, 8 Dec 2017 14:07:52 -0700 Subject: [PATCH 32/52] BUG: fix missing context in ingest-exchange, bug introduced in commit ce085e01ecb92375dd14ce552644280f2792f738 --- catalyst/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/catalyst/__main__.py b/catalyst/__main__.py index 9e6787b5..539a3c27 100644 --- a/catalyst/__main__.py +++ b/catalyst/__main__.py @@ -521,6 +521,7 @@ def live(ctx, default=False, help='Report potential anomalies found in data bundles.' ) +@click.pass_context def ingest_exchange(ctx, exchange_name, data_frequency, start, end, include_symbols, exclude_symbols, csv, show_progress, verbose, validate): From 6981669a6880ff0e3ddc8c3d1da07c14046af02b Mon Sep 17 00:00:00 2001 From: fredfortier Date: Fri, 8 Dec 2017 18:26:42 -0500 Subject: [PATCH 33/52] BUG: adding capital_base to the interface --- catalyst/examples/arbitrage_with_interface.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/catalyst/examples/arbitrage_with_interface.py b/catalyst/examples/arbitrage_with_interface.py index c0c0d343..67459e9a 100644 --- a/catalyst/examples/arbitrage_with_interface.py +++ b/catalyst/examples/arbitrage_with_interface.py @@ -263,13 +263,20 @@ def analyze(context, stats): pass -run_algorithm( - initialize=initialize, - handle_data=handle_data, - analyze=analyze, - exchange_name='poloniex,bitfinex', - live=True, - algo_namespace=algo_namespace, - quote_currency='btc', - live_graph=False -) +if __name__ == '__main__': + # The execution mode: backtest or live + MODE = 'live' + if MODE == 'live': + run_algorithm( + capital_base=0.1, + initialize=initialize, + handle_data=handle_data, + analyze=analyze, + exchange_name='poloniex,bitfinex', + live=True, + algo_namespace=algo_namespace, + base_currency='btc', + live_graph=False, + simulate_orders=True, + stats_output=None, + ) From e41eca0d8a4f51df9e40d3d99fbd56cd23542c47 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Fri, 8 Dec 2017 20:29:55 -0500 Subject: [PATCH 34/52] BUG: fixed some issues with capital_base --- catalyst/examples/mean_reversion_simple.py | 6 +- catalyst/examples/simple_loop.py | 4 +- catalyst/exchange/exchange_algorithm.py | 120 +++++++++++---------- catalyst/exchange/exchange_errors.py | 10 +- catalyst/exchange/stats_utils.py | 36 ++++--- catalyst/utils/run_algo.py | 39 ++++--- 6 files changed, 123 insertions(+), 92 deletions(-) diff --git a/catalyst/examples/mean_reversion_simple.py b/catalyst/examples/mean_reversion_simple.py index 4508c8e5..0e1c98d7 100644 --- a/catalyst/examples/mean_reversion_simple.py +++ b/catalyst/examples/mean_reversion_simple.py @@ -34,7 +34,7 @@ def initialize(context): # parameters or values you're going to use. # In our example, we're looking at Neo in Ether. - context.market = symbol('rdn_eth') + context.market = symbol('neo_eth') context.base_price = None context.current_day = None @@ -240,7 +240,7 @@ def analyze(context=None, perf=None): if __name__ == '__main__': # The execution mode: backtest or live - MODE = 'live' + MODE = 'backtest' if MODE == 'backtest': folder = os.path.join( @@ -259,7 +259,7 @@ if __name__ == '__main__': initialize=initialize, handle_data=handle_data, analyze=analyze, - exchange_name='bittrex', + exchange_name='bitfinex', algo_namespace=NAMESPACE, base_currency='eth', start=pd.to_datetime('2017-10-01', utc=True), diff --git a/catalyst/examples/simple_loop.py b/catalyst/examples/simple_loop.py index b0dbd929..9aa3b9f1 100644 --- a/catalyst/examples/simple_loop.py +++ b/catalyst/examples/simple_loop.py @@ -109,9 +109,9 @@ def analyze(context, perf): run_algorithm( capital_base=250, - start=pd.to_datetime('2017-11-1 0:00', utc=True), + start=pd.to_datetime('2017-11-9 0:00', utc=True), end=pd.to_datetime('2017-11-10 23:59', utc=True), - data_frequency='daily', + data_frequency='minute', initialize=initialize, handle_data=handle_data, analyze=analyze, diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index ff7dc94a..b98357d6 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -13,7 +13,6 @@ import pickle import signal import sys -from collections import deque from datetime import timedelta from os import listdir from os.path import isfile, join @@ -631,50 +630,72 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): 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() - - frame_stats = self.prepare_period_stats( - data.current_dt, data.current_dt + timedelta(minutes=1)) - - # Saving the last hour in memory - self.frame_stats.append(frame_stats) - - self.add_pnl_stats(frame_stats) - if self.recorded_vars: - self.add_custom_signals_stats(frame_stats) - recorded_cols = list(self.recorded_vars.keys()) - else: - recorded_cols = None - - self.add_exposure_stats(frame_stats) - - # print_df = pd.DataFrame(list(self.frame_stats)) - log.info( - 'statistics for the last {stats_minutes} minutes:\n' - '{stats}'.format( - stats_minutes=self.stats_minutes, - stats=get_pretty_stats( - stats=self.frame_stats, - recorded_cols=recorded_cols, - num_rows=self.stats_minutes - ) - )) - - daily_stats = self.prepare_period_stats( - start_dt=today, - end_dt=pd.Timestamp.utcnow() - ) - save_algo_object( - algo_name=self.algo_namespace, - key=today.strftime('%Y-%m-%d'), - obj=daily_stats, - rel_path='daily_perf' - ) + self._save_stats_csv(self._process_stats(data)) except Exception as e: log.warn('unable to calculate performance: {}'.format(e)) + # TODO: pickle does not seem to work in python 3 + try: + save_algo_object( + algo_name=self.algo_namespace, + key='perf_tracker', + obj=self.perf_tracker + ) + except Exception as e: + log.warn('unable to save minute perfs to disk: {}'.format(e)) + + self.current_day = data.current_dt.floor('1D') + + def _process_stats(self, data): + today = data.current_dt.floor('1D') + + # 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() + + frame_stats = self.prepare_period_stats( + data.current_dt, data.current_dt + timedelta(minutes=1)) + + # Saving the last hour in memory + self.frame_stats.append(frame_stats) + + self.add_pnl_stats(frame_stats) + if self.recorded_vars: + self.add_custom_signals_stats(frame_stats) + recorded_cols = list(self.recorded_vars.keys()) + + else: + recorded_cols = None + + self.add_exposure_stats(frame_stats) + + log.info( + 'statistics for the last {stats_minutes} minutes:\n' + '{stats}'.format( + stats_minutes=self.stats_minutes, + stats=get_pretty_stats( + stats=self.frame_stats, + recorded_cols=recorded_cols, + num_rows=self.stats_minutes + ) + )) + + # Saving the daily stats in a format usable for performance + # analysis. + daily_stats = self.prepare_period_stats( + start_dt=today, + end_dt=data.current_dt + ) + save_algo_object( + algo_name=self.algo_namespace, + key=today.strftime('%Y-%m-%d'), + obj=daily_stats, + rel_path='daily_perf' + ) + + return recorded_cols + + def _save_stats_csv(self, recorded_cols): # Writing the stats output csv_bytes = None try: @@ -683,7 +704,6 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): algo_namespace=self.algo_namespace, recorded_cols=recorded_cols, ) - except Exception as e: log.warn('unable save stats locally: {}'.format(e)) @@ -697,27 +717,13 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): recorded_cols=recorded_cols, bytes_to_write=csv_bytes ) - else: raise ValueError( 'Only S3 stats output is supported for now.' ) - except Exception as e: log.warn('unable save stats externally: {}'.format(e)) - # TODO: pickle does not seem to work in python 3 - try: - save_algo_object( - algo_name=self.algo_namespace, - key='perf_tracker', - obj=self.perf_tracker - ) - except Exception as e: - log.warn('unable to save minute perfs to disk: {}'.format(e)) - - self.current_day = data.current_dt.floor('1D') - @api_method def batch_market_order(self, share_counts): raise NotImplementedError() diff --git a/catalyst/exchange/exchange_errors.py b/catalyst/exchange/exchange_errors.py index 4f1acd9c..7744aa44 100644 --- a/catalyst/exchange/exchange_errors.py +++ b/catalyst/exchange/exchange_errors.py @@ -244,7 +244,7 @@ class NoDataAvailableOnExchange(ZiplineError): 'exchange {exchange} ' 'in `{data_frequency}` frequency at this time. ' 'Check `http://enigma.co/catalyst/status` for market coverage.' - ).strip() + ).strip() class NoValueForField(ZiplineError): @@ -255,3 +255,11 @@ class OrderTypeNotSupported(ZiplineError): msg = ( 'Order type `{order_type}` not currencly supported by Catalyst. ' 'Please use `limit` or `market` orders only.').strip() + + +class NotEnoughCapitalError(ZiplineError): + msg = ( + 'Not enough capital on exchange {exchange} for trading. Each ' + 'exchange should contain at least as much {base_currency} ' + 'as the specified `capital_base`. The current balance {balance} is ' + 'lower than the `capital_base`: {capital_base}').strip() diff --git a/catalyst/exchange/stats_utils.py b/catalyst/exchange/stats_utils.py index 737ad608..6c5d49bd 100644 --- a/catalyst/exchange/stats_utils.py +++ b/catalyst/exchange/stats_utils.py @@ -192,20 +192,21 @@ def prepare_stats(stats, recorded_cols=list()): assets = [p['sid'] for p in row_data['positions']] asset_values = dict() - for column in recorded_cols[:]: - value = row_data[column] - if type(value) is dict: - for asset in value: - if not isinstance(asset, TradingPair): - break + if recorded_cols is not None: + for column in recorded_cols[:]: + value = row_data[column] + if type(value) is dict: + for asset in value: + if not isinstance(asset, TradingPair): + break - if asset not in assets: - assets.append(asset) + if asset not in assets: + assets.append(asset) - if asset not in asset_values: - asset_values[asset] = dict() + if asset not in asset_values: + asset_values[asset] = dict() - asset_values[asset][column] = value[asset] + asset_values[asset][column] = value[asset] if len(assets) == 1: row = stats[row_index] @@ -231,8 +232,8 @@ def prepare_stats(stats, recorded_cols=list()): ] # Removing the asset specific entries - recorded_cols = [x for x in recorded_cols if x not in asset_cols] if recorded_cols is not None: + recorded_cols = [x for x in recorded_cols if x not in asset_cols] for column in recorded_cols: index_cols.append(column) @@ -256,13 +257,24 @@ def get_pretty_stats(stats, recorded_cols=None, num_rows=10): Parameters ---------- stats: list[Object] + An array of statistics for the period. + num_rows: int + The number of rows to display on the screen. Returns ------- str """ + if isinstance(stats, pd.DataFrame): + # df = stats + # columns = [ + # 'period_close', 'starting_cash', 'ending_cash', 'portfolio_value', + # 'pnl', 'long_exposure', 'short_exposure', 'orders', 'transactions', + # ] + stats = stats.T.to_dict().values() + # else: df, columns = prepare_stats(stats, recorded_cols=recorded_cols) pd.set_option('display.expand_frame_repr', False) diff --git a/catalyst/utils/run_algo.py b/catalyst/utils/run_algo.py index 3c62ea2d..a3442ec9 100644 --- a/catalyst/utils/run_algo.py +++ b/catalyst/utils/run_algo.py @@ -40,7 +40,7 @@ from catalyst.exchange.exchange_data_portal import DataPortalExchangeLive, \ from catalyst.exchange.asset_finder_exchange import AssetFinderExchange from catalyst.exchange.exchange_errors import ( ExchangeRequestError, ExchangeRequestErrorTooManyAttempts, - BaseCurrencyNotFoundError) + BaseCurrencyNotFoundError, NotEnoughCapitalError) from catalyst.constants import LOG_LEVEL @@ -227,28 +227,25 @@ def _run(handle_data, ) ) - if capital_base is not None \ - and capital_base < base_currency_available: - log.info( - 'using capital base limit: {} {}'.format( - capital_base, base_currency - ) - ) - amount = capital_base - else: - amount = base_currency_available - - return amount + return base_currency_available else: raise BaseCurrencyNotFoundError( base_currency=base_currency, exchange=exchange_name ) - combined_capital_base = 0 - for exchange_name in exchanges: - exchange = exchanges[exchange_name] - combined_capital_base += fetch_capital_base(exchange) + if not simulate_orders: + for exchange_name in exchanges: + exchange = exchanges[exchange_name] + balance = fetch_capital_base(exchange) + + if balance < capital_base: + raise NotEnoughCapitalError( + exchange=exchange_name, + base_currency=base_currency, + balance=balance, + capital_base=capital_base, + ) sim_params = create_simulation_parameters( start=start, @@ -505,6 +502,14 @@ def run_algorithm(initialize, default_extension, extensions, strict_extensions, environ ) + if capital_base is None: + raise ValueError( + 'Please specify a `capital_base` parameter which is the maximum ' + 'amount of base currency available for trading. For example, ' + 'if the `capital_base` is 5ETH, the ' + '`order_target_percent(asset, 1)` command will order 5ETH worth ' + 'of the specified asset.' + ) # I'm not sure that we need this since the modified DataPortal # does not require extensions to be explicitly loaded. From a74da3196451c660f13b9d40956256bd50028bec Mon Sep 17 00:00:00 2001 From: fredfortier Date: Sat, 9 Dec 2017 21:47:47 -0500 Subject: [PATCH 35/52] BLD: improved error handling of the tickers operations --- catalyst/exchange/ccxt/ccxt_exchange.py | 29 ++++++++++++++++--------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py index 6724a8c7..79203b5f 100644 --- a/catalyst/exchange/ccxt/ccxt_exchange.py +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -578,20 +578,29 @@ class CCXT(Exchange): """ tickers = dict() for asset in assets: - ccxt_symbol = self.get_symbol(asset) - ticker = self.api.fetch_ticker(ccxt_symbol) + try: + ccxt_symbol = self.get_symbol(asset) + ticker = self.api.fetch_ticker(ccxt_symbol) - ticker['last_traded'] = from_ms_timestamp(ticker['timestamp']) + ticker['last_traded'] = from_ms_timestamp(ticker['timestamp']) - if 'last_price' not in ticker: - # TODO: any more exceptions? - ticker['last_price'] = ticker['last'] + if 'last_price' not in ticker: + # TODO: any more exceptions? + ticker['last_price'] = ticker['last'] - # Using the volume represented in the base currency - ticker['volume'] = ticker['baseVolume'] \ - if 'baseVolume' in ticker else 0 + # Using the volume represented in the base currency + ticker['volume'] = ticker['baseVolume'] \ + if 'baseVolume' in ticker else 0 - tickers[asset] = ticker + tickers[asset] = ticker + + except ExchangeNotAvailable as e: + log.warn( + 'unable to fetch ticker: {} {}'.format( + self.name, asset.symbol + ) + ) + raise ExchangeRequestError(error=e) return tickers From 49aaaa8f2629f755a983a54689724281c46f0170 Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Sun, 10 Dec 2017 01:24:39 -0500 Subject: [PATCH 36/52] BLD: Housekeeping --- catalyst/examples/mean_reversion_simple.py | 12 +++++++----- catalyst/exchange/bitfinex/bitfinex.py | 14 ++++++++------ catalyst/exchange/bittrex/bittrex.py | 2 ++ catalyst/exchange/poloniex/poloniex.py | 6 ++++-- tests/exchange/test_bitfinex.py | 2 ++ tests/exchange/test_bittrex.py | 2 ++ tests/exchange/test_poloniex.py | 3 +++ 7 files changed, 28 insertions(+), 13 deletions(-) diff --git a/catalyst/examples/mean_reversion_simple.py b/catalyst/examples/mean_reversion_simple.py index 0e1c98d7..b3179cdd 100644 --- a/catalyst/examples/mean_reversion_simple.py +++ b/catalyst/examples/mean_reversion_simple.py @@ -38,8 +38,8 @@ def initialize(context): context.base_price = None context.current_day = None - context.RSI_OVERSOLD = 65 - context.RSI_OVERBOUGHT = 82 + context.RSI_OVERSOLD = 30 + context.RSI_OVERBOUGHT = 80 context.CANDLE_SIZE = '5T' context.start_time = time.time() @@ -98,9 +98,10 @@ def handle_data(context, data): # a parameter of the analyze() function for further analysis. record( - volume=(context.market, current['volume']), - price_change=(context.market, price_change), - rsi=(context.market, rsi[-1]), + volume=current['volume'], + price=price, + price_change=price_change, + rsi=rsi[-1], cash=cash ) # We are trying to avoid over-trading by limiting our trades to @@ -108,6 +109,7 @@ def handle_data(context, data): if context.traded_today: return + # TODO: retest with open orders # Since we are using limit orders, some orders may not execute immediately # we wait until all orders are executed before considering more trades. orders = get_open_orders(context.market) diff --git a/catalyst/exchange/bitfinex/bitfinex.py b/catalyst/exchange/bitfinex/bitfinex.py index fa6077ac..591e8486 100644 --- a/catalyst/exchange/bitfinex/bitfinex.py +++ b/catalyst/exchange/bitfinex/bitfinex.py @@ -30,15 +30,17 @@ from catalyst.protocol import Account # Trying to account for REST api instability # https://stackoverflow.com/questions/15431044/can-i-set-max-retries-for-requests-request +from catalyst.utils.deprecate import deprecated + requests.adapters.DEFAULT_RETRIES = 20 BITFINEX_URL = 'https://api.bitfinex.com' - log = Logger('Bitfinex', level=LOG_LEVEL) warning_logger = Logger('AlgoWarning') +@deprecated class Bitfinex(Exchange): def __init__(self, key, secret, base_currency, portfolio=None): self.url = BITFINEX_URL @@ -666,11 +668,11 @@ class Bitfinex(Exchange): """ url = ('{url}/v2/candles/trade:1D:{symbol}/hist?start={start}' '&end={end}').format( - url=self.url, - symbol=symbol_v2, - start=startmonth - 3600 * 24 * 31 * 1000, - end=min(startmonth + 3600 * 24 * 31 * 1000, - int(time.time() * 1000))) + url=self.url, + symbol=symbol_v2, + start=startmonth - 3600 * 24 * 31 * 1000, + end=min(startmonth + 3600 * 24 * 31 * 1000, + int(time.time() * 1000))) try: self.ask_request() diff --git a/catalyst/exchange/bittrex/bittrex.py b/catalyst/exchange/bittrex/bittrex.py index 5835b8e1..3057d501 100644 --- a/catalyst/exchange/bittrex/bittrex.py +++ b/catalyst/exchange/bittrex/bittrex.py @@ -19,12 +19,14 @@ from catalyst.finance.execution import LimitOrder, StopLimitOrder from catalyst.finance.order import Order, ORDER_STATUS # TODO: consider using this: https://github.com/mondeja/bittrex_v2 +from catalyst.utils.deprecate import deprecated log = Logger('Bittrex', level=LOG_LEVEL) URL2 = 'https://bittrex.com/Api/v2.0' +@deprecated class Bittrex(Exchange): def __init__(self, key, secret, base_currency, portfolio=None): self.api = Bittrex_api(key=key, secret=secret) diff --git a/catalyst/exchange/poloniex/poloniex.py b/catalyst/exchange/poloniex/poloniex.py index b29b0e92..49ccbd40 100644 --- a/catalyst/exchange/poloniex/poloniex.py +++ b/catalyst/exchange/poloniex/poloniex.py @@ -28,10 +28,12 @@ from catalyst.exchange.poloniex.poloniex_api import Poloniex_api from catalyst.finance.order import Order, ORDER_STATUS from catalyst.finance.transaction import Transaction from catalyst.protocol import Account +from catalyst.utils.deprecate import deprecated log = Logger('Poloniex', level=LOG_LEVEL) +@deprecated class Poloniex(Exchange): def __init__(self, key, secret, base_currency, portfolio=None): self.api = Poloniex_api(key=key, secret=secret) @@ -292,8 +294,8 @@ class Poloniex(Exchange): """ exchange_symbol = self.get_symbol(asset) - if(isinstance(style, ExchangeLimitOrder) - or isinstance(style, ExchangeStopLimitOrder)): + if (isinstance(style, ExchangeLimitOrder) + or isinstance(style, ExchangeStopLimitOrder)): if isinstance(style, ExchangeStopLimitOrder): log.warn('{} will ignore the stop price'.format(self.name)) diff --git a/tests/exchange/test_bitfinex.py b/tests/exchange/test_bitfinex.py index 1859f38f..4ac4e205 100644 --- a/tests/exchange/test_bitfinex.py +++ b/tests/exchange/test_bitfinex.py @@ -4,10 +4,12 @@ from base import BaseExchangeTestCase from catalyst.exchange.bitfinex.bitfinex import Bitfinex from catalyst.exchange.exchange_utils import get_exchange_auth from catalyst.finance.execution import (LimitOrder) +from catalyst.utils.deprecate import deprecated log = Logger('test_bitfinex') +@deprecated class TestBitfinex(BaseExchangeTestCase): @classmethod def setup(self): diff --git a/tests/exchange/test_bittrex.py b/tests/exchange/test_bittrex.py index 47d54068..d77c67b0 100644 --- a/tests/exchange/test_bittrex.py +++ b/tests/exchange/test_bittrex.py @@ -4,10 +4,12 @@ from catalyst.finance.order import Order from base import BaseExchangeTestCase from logbook import Logger from catalyst.exchange.exchange_utils import get_exchange_auth +from catalyst.utils.deprecate import deprecated log = Logger('test_bittrex') +@deprecated class TestBittrex(BaseExchangeTestCase): @classmethod def setup(self): diff --git a/tests/exchange/test_poloniex.py b/tests/exchange/test_poloniex.py index 62800468..f263e9b0 100644 --- a/tests/exchange/test_poloniex.py +++ b/tests/exchange/test_poloniex.py @@ -4,11 +4,14 @@ from base import BaseExchangeTestCase from logbook import Logger from catalyst.exchange.exchange_utils import get_exchange_auth import pandas as pd + +from catalyst.utils.deprecate import deprecated from test_utils import output_df log = Logger('test_poloniex') +@deprecated class TestPoloniex(BaseExchangeTestCase): @classmethod def setup(self): From a3761beae264bc2d92a9939c959b0d1902ae8f41 Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Mon, 11 Dec 2017 02:22:38 -0500 Subject: [PATCH 37/52] BUG: fixed retry issue with synchronize_portfolio --- catalyst/exchange/exchange_algorithm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index b98357d6..2fb70cc8 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -507,7 +507,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): ) if attempt_index < self.retry_synchronize_portfolio: sleep(self.retry_delay) - self.synchronize_portfolio(attempt_index + 1) + return self.synchronize_portfolio(attempt_index + 1) else: raise ExchangePortfolioDataError( data_type='update-portfolio', From 153469664c90dad798b5455c7bf1a13fe9e04258 Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Mon, 11 Dec 2017 12:31:30 -0700 Subject: [PATCH 38/52] DOC: link README to DOC landing page, added badges --- README.rst | 4 ++-- docs/source/index.rst | 2 +- docs/source/welcome.rst | 43 ----------------------------------------- 3 files changed, 3 insertions(+), 46 deletions(-) delete mode 100644 docs/source/welcome.rst diff --git a/README.rst b/README.rst index 1dc87f7d..6c395b45 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ .. image:: https://s3.amazonaws.com/enigmaco-docs/enigma-catalyst.jpg - :target: https://enigmampc.github.io/catalyst - :align: center + :target: https://enigmampc.github.io/catalyst + :align: center :alt: Enigma | Catalyst |version tag| diff --git a/docs/source/index.rst b/docs/source/index.rst index a0e8f097..3e94084f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,4 +1,4 @@ -.. include:: welcome.rst +.. include:: ../../README.rst | | Table of Contents diff --git a/docs/source/welcome.rst b/docs/source/welcome.rst deleted file mode 100644 index 22bd37ff..00000000 --- a/docs/source/welcome.rst +++ /dev/null @@ -1,43 +0,0 @@ -.. image:: https://s3.amazonaws.com/enigmaco-docs/enigma-catalyst.jpg -| -Catalyst is an algorithmic trading library for crypto-assets written in Python. -It allows trading strategies to be easily expressed and backtested against -historical data (with daily and minute resolution), providing analytics and -insights regarding a particular strategy's performance. Catalyst also supports -live-trading of crypto-assets starting with three exchanges (Bitfinex, Bittrex, -and Poloniex) with more being added over time. Catalyst empowers users to share -and curate data and build profitable, data-driven investment strategies. Please -visit `enigma.co `_ to learn more about Catalyst, or -refer to the `whitepaper `_ for -further technical details. - -Catalyst builds on top of the well-established -`Zipline `_ project. We did our best to -minimize structural changes to the general API to maximize compatibility with -existing trading algorithms, developer knowledge, and tutorials. Join us on -`Discord `_ where we have a *#catalyst_dev* channel -for questions around Catalyst, algorithmic trading and technical support. - -Features -======== - -- Ease of use: Catalyst tries to get out of your way so that you can - focus on algorithm development. See - `examples of trading strategies `_ - provided. -- Support for several of the top crypto-exchanges by trading volume: - `Bitfinex `_, `Bittrex `_, - and `Poloniex `_. -- Secure: You and only you have access to each exchange API keys for your accounts. -- Input of historical pricing data of all crypto-assets by exchange, - with daily and minute resolution. See - `Catalyst Market Coverage Overview `_. -- Backtesting and live-trading functionality, with a seamless transition - between the two modes. -- Output of performance statistics are based on Pandas DataFrames to - integrate nicely into the existing PyData eco-system. -- Statistic and machine learning libraries like matplotlib, scipy, - statsmodels, and sklearn support development, analysis, and - visualization of state-of-the-art trading systems. -- Addition of Bitcoin price (btc_usdt) as a benchmark for comparing - performance across trading algorithms. \ No newline at end of file From 5d84f26b7274a3bf71ba3e1d26ca1550e1c14f9e Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Mon, 11 Dec 2017 15:52:55 -0700 Subject: [PATCH 39/52] DOC: Updated Jupyter documentation --- docs/source/jupyter.rst | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/source/jupyter.rst b/docs/source/jupyter.rst index 4042da53..21fc010a 100644 --- a/docs/source/jupyter.rst +++ b/docs/source/jupyter.rst @@ -1,8 +1,7 @@ Catalyst & Jupyter Notebook =========================== -(Feel free to check out the actual Notebook file -`here `__) +(`This is actual Notebook `_ referenced in the text below) The `Jupyter Notebook `__ is a very powerful browser-based interface to a Python interpreter. As it is already the @@ -33,11 +32,19 @@ the interface through which you will interact with Jupyter Notebook. Running Algorithms ^^^^^^^^^^^^^^^^^^ -To use it you have to write your algorithm in a cell and let -``catalyst`` know that it is supposed to run this algorithm. This is -done via the ``%%catalyst`` IPython magic command that is available -after you import ``catalyst`` from within the Notebook. This magic takes -the same arguments as the command line interface. Thus to run the +Before running your algorithms inside the Jupyter Notebook, remember to ingest +the data from the command line interface (CLI). In the example below, you would +need to run first: + +.. code:: bash + + catalyst ingest-exchange -x bitfinex -i btc_usd + +To use Catalyst inside a Jupyter Noebook, you have to write your algorithm in a +cell and let the Jupyter know that it is supposed to execute this algorithm with +Catalyst. This is done via the ``%%catalyst`` IPython magic command that is +available after you import ``catalyst`` from within the Notebook. This magic +takes the same arguments as the command line interface. Thus to run the algorithm just supply the same parameters as the CLI but without the -f and -o arguments. We just have to execute the following cell after importing ``catalyst`` to register the magic. @@ -58,7 +65,7 @@ functions. .. code:: python - %%catalyst --start 2015-3-2 --end 2017-6-28 --capital-base 100000 -x bitfinex + %%catalyst --start 2015-3-2 --end 2017-6-28 --capital-base 100000 -x bitfinex -c usd from catalyst.finance.slippage import VolumeShareSlippage @@ -199,8 +206,6 @@ functions. .. figure:: https://i.imgur.com/DS5w47q.png :alt: png - png - .. raw:: html
From 42f59e99df10b255814dbf87c05b9e45b7cec360 Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Mon, 11 Dec 2017 20:00:45 -0500 Subject: [PATCH 40/52] BLD: improving stats upload --- catalyst/exchange/stats_utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/catalyst/exchange/stats_utils.py b/catalyst/exchange/stats_utils.py index 6c5d49bd..052f3e2d 100644 --- a/catalyst/exchange/stats_utils.py +++ b/catalyst/exchange/stats_utils.py @@ -338,11 +338,13 @@ def stats_to_s3(uri, stats, algo_namespace, recorded_cols=None, if bytes_to_write is None: bytes_to_write = get_csv_stats(stats, recorded_cols=recorded_cols) - timestr = time.strftime('%Y%m%d') + now = pd.Timestamp.utcnow() + timestr = now.strftime('%Y%m%d') + pid = os.getpid() parts = uri.split('//') - obj = s3.Object(parts[1], '{}/{}-{}.csv'.format( - folder, timestr, algo_namespace + obj = s3.Object(parts[1], '{}/{}-{}-{}.csv'.format( + folder, timestr, algo_namespace, pid )) obj.put(Body=bytes_to_write) From 552f4260b4a4b290545ab14c33c49f1b8c2376e3 Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Mon, 11 Dec 2017 22:34:46 -0500 Subject: [PATCH 41/52] BLD: for issue #87, added configurable slippage and commission --- catalyst/examples/mean_reversion_simple.py | 3 ++ catalyst/exchange/exchange_algorithm.py | 16 +++++++ catalyst/exchange/exchange_blotter.py | 54 ++++++++++++---------- 3 files changed, 48 insertions(+), 25 deletions(-) diff --git a/catalyst/examples/mean_reversion_simple.py b/catalyst/examples/mean_reversion_simple.py index b3179cdd..b3fb7934 100644 --- a/catalyst/examples/mean_reversion_simple.py +++ b/catalyst/examples/mean_reversion_simple.py @@ -44,6 +44,9 @@ def initialize(context): context.start_time = time.time() + # context.set_commission(maker=0.1, taker=0.2) + context.set_slippage(spread=0.0001) + def handle_data(context, data): # This handle_data function is where the real work is done. Our data is diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index 2fb70cc8..3505accf 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -23,6 +23,7 @@ import pandas as pd import catalyst.protocol as zp from catalyst.algorithm import TradingAlgorithm +from catalyst.assets._assets import TradingPair from catalyst.constants import LOG_LEVEL from catalyst.exchange.exchange_blotter import ExchangeBlotter from catalyst.exchange.exchange_errors import ( @@ -113,6 +114,21 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm): else: return MarketOrder() + @api_method + def set_commission(self, maker=None, taker=None): + key = self.blotter.commission_models.keys()[0] + if maker is not None: + self.blotter.commission_models[key].maker = maker + + if taker is not None: + self.blotter.commission_models[key].taker = taker + + @api_method + def set_slippage(self, spread=None): + key = self.blotter.slippage_models.keys()[0] + if spread is not None: + self.blotter.slippage_models[key].spread = spread + def _calculate_order(self, asset, amount, limit_price=None, stop_price=None, style=None): # Raises a ZiplineError if invalid parameters are detected. diff --git a/catalyst/exchange/exchange_blotter.py b/catalyst/exchange/exchange_blotter.py index 7b89f9b9..ab8056d2 100644 --- a/catalyst/exchange/exchange_blotter.py +++ b/catalyst/exchange/exchange_blotter.py @@ -9,20 +9,13 @@ from catalyst.exchange.exchange_errors import ExchangeRequestError, \ ExchangePortfolioDataError, ExchangeTransactionError from catalyst.finance.blotter import Blotter from catalyst.finance.commission import CommissionModel -from catalyst.finance.order import ORDER_STATUS +from catalyst.finance.order import ORDER_STATUS, Order from catalyst.finance.slippage import SlippageModel from catalyst.finance.transaction import create_transaction, Transaction from catalyst.utils.input_validation import expect_types log = Logger('exchange_blotter', level=LOG_LEVEL) -# It seems like we need to accept greater slippage risk in cryptos -# Orders won't often close at Equity levels. -# TODO: should work with set_commission and set_slippage -DEFAULT_SLIPPAGE_SPREAD = 0.0001 -DEFAULT_MAKER_FEE = 0.0015 -DEFAULT_TAKER_FEE = 0.0025 - class TradingPairFeeSchedule(CommissionModel): """ @@ -30,23 +23,24 @@ class TradingPairFeeSchedule(CommissionModel): Parameters ---------- - fee : float, optional - The percentage fee. + maker : float, optional + The percentage maker fee. + + taker: float, optional + The percentage taker fee. """ - def __init__(self, - maker_fee=DEFAULT_MAKER_FEE, - taker_fee=DEFAULT_TAKER_FEE): - self.maker_fee = maker_fee - self.taker_fee = taker_fee + def __init__(self, maker=None, taker=None): + self.maker = maker + self.taker = taker def __repr__(self): return ( - '{class_name}(maker_fee={maker_fee}, ' - 'taker_fee={taker_fee})'.format( + '{class_name}(maker={maker}, ' + 'taker={taker})'.format( class_name=self.__class__.__name__, - maker_fee=self.maker_fee, - taker_fee=self.taker_fee, + maker=self.maker, + taker=self.taker, ) ) @@ -54,16 +48,25 @@ class TradingPairFeeSchedule(CommissionModel): """ Calculate the final fee based on the order parameters. - :param order: - :param transaction: + :param order: Order + :param transaction: Transaction :return float: The total commission. """ cost = abs(transaction.amount) * transaction.price + asset = order.asset + maker = self.maker if self.maker is not None else asset.maker + taker = self.taker if self.taker is not None else asset.taker + + multiplier = maker \ + if ((order.amount > 0 and order.limit < transaction.price) + or (order.amount < 0 and order.limit > transaction.price)) \ + and order.limit_reached else taker + # Assuming just the taker fee for now - fee = cost * self.taker_fee + fee = cost * multiplier return fee @@ -77,7 +80,7 @@ class TradingPairFixedSlippage(SlippageModel): spread / 2 will be added to buys and subtracted from sells. """ - def __init__(self, spread=DEFAULT_SLIPPAGE_SPREAD): + def __init__(self, spread=0.0001): super(TradingPairFixedSlippage, self).__init__() self.spread = spread @@ -132,8 +135,9 @@ class ExchangeBlotter(Blotter): self.exchanges = kwargs.pop('exchanges', None) if not self.exchanges: - raise ValueError('ExchangeBlotter must have an `exchanges` ' - 'attribute.') + raise ValueError( + 'ExchangeBlotter must have an `exchanges` attribute.' + ) super(ExchangeBlotter, self).__init__(*args, **kwargs) From 1d15e12b8df16cf9a02ed3a47df16af846d5d47e Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Tue, 12 Dec 2017 09:04:30 -0700 Subject: [PATCH 42/52] DOC: added features page, restructured Jupyter & naming convention --- docs/source/beginner-tutorial.rst | 15807 +++++++++++++++- .../{naming-convention.rst => features.rst} | 53 +- docs/source/index.rst | 4 +- docs/source/jupyter.rst | 15799 --------------- 4 files changed, 15858 insertions(+), 15805 deletions(-) rename docs/source/{naming-convention.rst => features.rst} (59%) delete mode 100644 docs/source/jupyter.rst diff --git a/docs/source/beginner-tutorial.rst b/docs/source/beginner-tutorial.rst index 38603888..2b34bba1 100644 --- a/docs/source/beginner-tutorial.rst +++ b/docs/source/beginner-tutorial.rst @@ -132,8 +132,8 @@ data is managed, hence the following overview: different bundle datasets, and are managed separately. - Bundles are exchange-specific, as the pricing data is specific to the trades - that happen in each exchange. As a result, you can must specify which - exchange you want pricing data from when ingesting data + that happen in each exchange. As a result, you must specify which + exchange you want pricing data from when ingesting data. - Catalyst keeps track of all the downloaded bundles, so that it only has to download them once, and will do incremental updates as needed. @@ -806,6 +806,15809 @@ the ``scikit-learn`` functions require ``numpy.ndarray``\ s rather than ``ndarray`` of a ``DataFrame`` via ``.values``). +Jupyter Notebook +~~~~~~~~~~~~~~~~ + +(`This is actual Notebook `_ referenced in the text below) + +The `Jupyter Notebook `__ is a very powerful +browser-based interface to a Python interpreter. As it is already the +de-facto interface for most quantitative researchers, ``catalyst`` +provides an easy way to run your algorithm inside the Notebook without +requiring you to use the CLI. We include this section here as an alternative to +running algorithms through the command line. + +Install +^^^^^^^ + +In order to use Jupyter Notebook, you first have to install it inside your +environment. It's available as ``pip`` package, so regardless of how you +installed Catalyst, go inside your catalyst environemnt and run: + +.. code:: bash + + (catalyst)$ pip install jupyter + +Once you have Jupyter Notebook installed, every time you want to use it run: + +.. code:: bash + + (catalyst)$ jupyter notebook + +A local server will launch, and will open a new window on your browser. That's +the interface through which you will interact with Jupyter Notebook. + +Running Algorithms +^^^^^^^^^^^^^^^^^^ + +Before running your algorithms inside the Jupyter Notebook, remember to ingest +the data from the command line interface (CLI). In the example below, you would +need to run first: + +.. code:: bash + + catalyst ingest-exchange -x bitfinex -i btc_usd + +To use Catalyst inside a Jupyter Noebook, you have to write your algorithm in a +cell and let the Jupyter know that it is supposed to execute this algorithm with +Catalyst. This is done via the ``%%catalyst`` IPython magic command that is +available after you import ``catalyst`` from within the Notebook. This magic +takes the same arguments as the command line interface. Thus to run the +algorithm just supply the same parameters as the CLI but without the -f +and -o arguments. We just have to execute the following cell after +importing ``catalyst`` to register the magic. + +.. code:: python + + # Register the catalyst magic + %load_ext catalyst + +.. code:: python + + # Setup matplotlib to display graphs inline in this Notebook + %matplotlib inline + +Note below that we do not have to specify an input file (-f) since the +magic will use the contents of the cell and look for your algorithm +functions. + +.. code:: python + + %%catalyst --start 2015-3-2 --end 2017-6-28 --capital-base 100000 -x bitfinex -c usd + + from catalyst.finance.slippage import VolumeShareSlippage + + from catalyst.api import ( + order_target_value, + symbol, + record, + cancel_order, + get_open_orders, + ) + + def initialize(context): + context.ASSET_NAME = 'btc_usd' + context.TARGET_HODL_RATIO = 0.8 + context.RESERVE_RATIO = 1.0 - context.TARGET_HODL_RATIO + + # For all trading pairs in the poloniex bundle, the default denomination + # currently supported by Catalyst is 1/1000th of a full coin. Use this + # constant to scale the price of up to that of a full coin if desired. + context.TICK_SIZE = 1000.0 + + context.is_buying = True + context.asset = symbol(context.ASSET_NAME) + + context.i = 0 + + def handle_data(context, data): + context.i += 1 + + starting_cash = context.portfolio.starting_cash + target_hodl_value = context.TARGET_HODL_RATIO * starting_cash + reserve_value = context.RESERVE_RATIO * starting_cash + + # Cancel any outstanding orders + orders = get_open_orders(context.asset) or [] + for order in orders: + cancel_order(order) + + # Stop buying after passing the reserve threshold + cash = context.portfolio.cash + if cash <= reserve_value: + context.is_buying = False + + # Retrieve current asset price from pricing data + price = data.current(context.asset, 'price') + + # 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_target_value( + context.asset, + target_hodl_value, + limit_price=price*1.1, + stop_price=price*0.9, + ) + + record( + price=price, + volume=data.current(context.asset, 'volume'), + cash=cash, + starting_cash=context.portfolio.starting_cash, + leverage=context.account.leverage, + ) + + def analyze(context=None, results=None): + import matplotlib.pyplot as plt + + # Plot the portfolio and asset data. + ax1 = plt.subplot(611) + results[['portfolio_value']].plot(ax=ax1) + ax1.set_ylabel('Portfolio Value (USD)') + + ax2 = plt.subplot(612, sharex=ax1) + ax2.set_ylabel('{asset} (USD)'.format(asset=context.ASSET_NAME)) + (context.TICK_SIZE * results[['price']]).plot(ax=ax2) + + trans = results.ix[[t != [] for t in results.transactions]] + buys = trans.ix[ + [t[0]['amount'] > 0 for t in trans.transactions] + ] + ax2.plot( + buys.index, + context.TICK_SIZE * results.price[buys.index], + '^', + markersize=10, + color='g', + ) + + ax3 = plt.subplot(613, sharex=ax1) + results[['leverage', 'alpha', 'beta']].plot(ax=ax3) + ax3.set_ylabel('Leverage ') + + ax4 = plt.subplot(614, sharex=ax1) + results[['starting_cash', 'cash']].plot(ax=ax4) + ax4.set_ylabel('Cash (USD)') + + results[[ + 'treasury', + 'algorithm', + 'benchmark', + ]] = results[[ + 'treasury_period_return', + 'algorithm_period_return', + 'benchmark_period_return', + ]] + + ax5 = plt.subplot(615, sharex=ax1) + results[[ + 'treasury', + 'algorithm', + 'benchmark', + ]].plot(ax=ax5) + ax5.set_ylabel('Percent Change') + + ax6 = plt.subplot(616, sharex=ax1) + results[['volume']].plot(ax=ax6) + ax6.set_ylabel('Volume (mCoins/5min)') + + plt.legend(loc=3) + + # Show the plot. + plt.gcf().set_size_inches(18, 8) + plt.show() + +:: + + [2017-08-11 07:19:46.411748] INFO: Loader: Loading benchmark data for 'USDT_BTC' from 1989-12-31 00:00:00+00:00 to 2017-08-09 00:00:00+00:00 + [2017-08-11 07:19:46.418983] INFO: Loader: Loading data for /Users//.catalyst/data/USDT_BTC_benchmark.csv failed with error [Unknown string format]. + [2017-08-11 07:19:46.419740] INFO: Loader: Cache at /Users//.catalyst/data/USDT_BTC_benchmark.csv does not have data from 1990-01-01 00:00:00+00:00 to 2017-08-09 00:00:00+00:00. + + [2017-08-11 07:19:46.420770] INFO: Loader: Downloading benchmark data for 'USDT_BTC' from 1989-12-31 00:00:00+00:00 to 2017-08-09 00:00:00+00:00 + [2017-08-11 07:19:50.060244] WARNING: Loader: Still don't have expected data after redownload! + [2017-08-11 07:19:50.097334] WARNING: Loader: Refusing to download new treasury data because a download succeeded at 2017-08-11 06:56:49+00:00. + [2017-08-11 07:19:54.618399] INFO: Performance: Simulated 851 trading days out of 851. + [2017-08-11 07:19:54.619301] INFO: Performance: first open: 2015-03-01 00:00:00+00:00 + [2017-08-11 07:19:54.620430] INFO: Performance: last close: 2017-06-28 23:59:00+00:00 + +.. figure:: https://i.imgur.com/DS5w47q.png + :alt: png + +.. raw:: html + +
+ +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + +
+ +.. raw:: html + + + +algo_volatility + +.. raw:: html + + + +algorithm_period_return + +.. raw:: html + + + +alpha + +.. raw:: html + + + +benchmark_period_return + +.. raw:: html + + + +benchmark_volatility + +.. raw:: html + + + +beta + +.. raw:: html + + + +capital_used + +.. raw:: html + + + +cash + +.. raw:: html + + + +ending_cash + +.. raw:: html + + + +ending_exposure + +.. raw:: html + + + +… + +.. raw:: html + + + +starting_cash + +.. raw:: html + + + +starting_exposure + +.. raw:: html + + + +starting_value + +.. raw:: html + + + +trading_days + +.. raw:: html + + + +transactions + +.. raw:: html + + + +treasury_period_return + +.. raw:: html + + + +volume + +.. raw:: html + + + +treasury + +.. raw:: html + + + +algorithm + +.. raw:: html + + + +benchmark + +.. raw:: html + +
+ +2015-03-01 23:59:00+00:00 + +.. raw:: html + + + +NaN + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +NaN + +.. raw:: html + + + +0.045833 + +.. raw:: html + + + +NaN + +.. raw:: html + + + +NaN + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +100000.000000 + +.. raw:: html + + + +100000.000000 + +.. raw:: html + + + +0.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +0.000 + +.. raw:: html + + + +0.000 + +.. raw:: html + + + +1 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0200 + +.. raw:: html + + + +317 + +.. raw:: html + + + +0.0200 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +0.045833 + +.. raw:: html + +
+ +2015-03-02 23:59:00+00:00 + +.. raw:: html + + + +0.000278 + +.. raw:: html + + + +-0.000025 + +.. raw:: html + + + +0.011045 + +.. raw:: html + + + +0.120833 + +.. raw:: html + + + +0.290503 + +.. raw:: html + + + +-0.000956 + +.. raw:: html + + + +-85544.474955 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +85542.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +0.000 + +.. raw:: html + + + +0.000 + +.. raw:: html + + + +2 + +.. raw:: html + + + +[{u’commission’: None, u’amount’: 318, u’sid’:… + +.. raw:: html + + + +0.0208 + +.. raw:: html + + + +98063 + +.. raw:: html + + + +0.0208 + +.. raw:: html + + + +-0.000025 + +.. raw:: html + + + +0.120833 + +.. raw:: html + +
+ +2015-03-03 23:59:00+00:00 + +.. raw:: html + + + +0.051796 + +.. raw:: html + + + +-0.005688 + +.. raw:: html + + + +-1.197544 + +.. raw:: html + + + +0.113416 + +.. raw:: html + + + +0.633538 + +.. raw:: html + + + +0.077239 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +84975.642 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +85542.000 + +.. raw:: html + + + +85542.000 + +.. raw:: html + + + +3 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0212 + +.. raw:: html + + + +442983 + +.. raw:: html + + + +0.0212 + +.. raw:: html + + + +-0.005688 + +.. raw:: html + + + +0.113416 + +.. raw:: html + +
+ +2015-03-04 23:59:00+00:00 + +.. raw:: html + + + +0.342118 + +.. raw:: html + + + +0.034955 + +.. raw:: html + + + +0.401861 + +.. raw:: html + + + +0.166666 + +.. raw:: html + + + +0.524400 + +.. raw:: html + + + +0.181468 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +89040.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +84975.642 + +.. raw:: html + + + +84975.642 + +.. raw:: html + + + +4 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0212 + +.. raw:: html + + + +245889 + +.. raw:: html + + + +0.0212 + +.. raw:: html + + + +0.034955 + +.. raw:: html + + + +0.166666 + +.. raw:: html + +
+ +2015-03-05 23:59:00+00:00 + +.. raw:: html + + + +0.637226 + +.. raw:: html + + + +-0.038185 + +.. raw:: html + + + +-3.914003 + +.. raw:: html + + + +0.070834 + +.. raw:: html + + + +0.976896 + +.. raw:: html + + + +0.550520 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +81726.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +89040.000 + +.. raw:: html + + + +89040.000 + +.. raw:: html + + + +5 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0211 + +.. raw:: html + + + +117440 + +.. raw:: html + + + +0.0211 + +.. raw:: html + + + +-0.038185 + +.. raw:: html + + + +0.070834 + +.. raw:: html + +
+ +2015-03-06 23:59:00+00:00 + +.. raw:: html + + + +0.580521 + +.. raw:: html + + + +-0.028645 + +.. raw:: html + + + +-3.100822 + +.. raw:: html + + + +0.083333 + +.. raw:: html + + + +0.874082 + +.. raw:: html + + + +0.546703 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +82680.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +81726.000 + +.. raw:: html + + + +81726.000 + +.. raw:: html + + + +6 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0224 + +.. raw:: html + + + +84197 + +.. raw:: html + + + +0.0224 + +.. raw:: html + + + +-0.028645 + +.. raw:: html + + + +0.083333 + +.. raw:: html + +
+ +2015-03-07 23:59:00+00:00 + +.. raw:: html + + + +0.530557 + +.. raw:: html + + + +-0.028645 + +.. raw:: html + + + +-2.625704 + +.. raw:: html + + + +0.083333 + +.. raw:: html + + + +0.802793 + +.. raw:: html + + + +0.536589 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +82680.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +82680.000 + +.. raw:: html + + + +82680.000 + +.. raw:: html + + + +7 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0224 + +.. raw:: html + + + +181 + +.. raw:: html + + + +0.0224 + +.. raw:: html + + + +-0.028645 + +.. raw:: html + + + +0.083333 + +.. raw:: html + +
+ +2015-03-08 23:59:00+00:00 + +.. raw:: html + + + +0.491628 + +.. raw:: html + + + +-0.028645 + +.. raw:: html + + + +-2.276841 + +.. raw:: html + + + +0.083333 + +.. raw:: html + + + +0.746605 + +.. raw:: html + + + +0.529163 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +82680.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +82680.000 + +.. raw:: html + + + +82680.000 + +.. raw:: html + + + +8 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0224 + +.. raw:: html + + + +30900 + +.. raw:: html + + + +0.0224 + +.. raw:: html + + + +-0.028645 + +.. raw:: html + + + +0.083333 + +.. raw:: html + +
+ +2015-03-09 23:59:00+00:00 + +.. raw:: html + + + +0.467885 + +.. raw:: html + + + +-0.015925 + +.. raw:: html + + + +-1.895269 + +.. raw:: html + + + +0.100000 + +.. raw:: html + + + +0.698764 + +.. raw:: html + + + +0.532652 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +83952.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +82680.000 + +.. raw:: html + + + +82680.000 + +.. raw:: html + + + +9 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0220 + +.. raw:: html + + + +128367 + +.. raw:: html + + + +0.0220 + +.. raw:: html + + + +-0.015925 + +.. raw:: html + + + +0.100000 + +.. raw:: html + +
+ +2015-03-10 23:59:00+00:00 + +.. raw:: html + + + +0.626552 + +.. raw:: html + + + +0.069935 + +.. raw:: html + + + +-1.625285 + +.. raw:: html + + + +0.212500 + +.. raw:: html + + + +0.800983 + +.. raw:: html + + + +0.676289 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +92538.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +83952.000 + +.. raw:: html + + + +83952.000 + +.. raw:: html + + + +10 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0214 + +.. raw:: html + + + +54961 + +.. raw:: html + + + +0.0214 + +.. raw:: html + + + +0.069935 + +.. raw:: html + + + +0.212500 + +.. raw:: html + +
+ +2015-03-11 23:59:00+00:00 + +.. raw:: html + + + +0.644515 + +.. raw:: html + + + +0.022235 + +.. raw:: html + + + +-1.727710 + +.. raw:: html + + + +0.150000 + +.. raw:: html + + + +0.834650 + +.. raw:: html + + + +0.684052 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +87768.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +92538.000 + +.. raw:: html + + + +92538.000 + +.. raw:: html + + + +11 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0211 + +.. raw:: html + + + +42511 + +.. raw:: html + + + +0.0211 + +.. raw:: html + + + +0.022235 + +.. raw:: html + + + +0.150000 + +.. raw:: html + +
+ +2015-03-12 23:59:00+00:00 + +.. raw:: html + + + +0.614650 + +.. raw:: html + + + +0.022235 + +.. raw:: html + + + +-1.573455 + +.. raw:: html + + + +0.150000 + +.. raw:: html + + + +0.798403 + +.. raw:: html + + + +0.680882 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +87768.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +87768.000 + +.. raw:: html + + + +87768.000 + +.. raw:: html + + + +12 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0210 + +.. raw:: html + + + +2909 + +.. raw:: html + + + +0.0210 + +.. raw:: html + + + +0.022235 + +.. raw:: html + + + +0.150000 + +.. raw:: html + +
+ +2015-03-13 23:59:00+00:00 + +.. raw:: html + + + +0.588942 + +.. raw:: html + + + +0.019405 + +.. raw:: html + + + +-1.454733 + +.. raw:: html + + + +0.146291 + +.. raw:: html + + + +0.767688 + +.. raw:: html + + + +0.677881 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +87484.980 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +87768.000 + +.. raw:: html + + + +87768.000 + +.. raw:: html + + + +13 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0213 + +.. raw:: html + + + +57613 + +.. raw:: html + + + +0.0213 + +.. raw:: html + + + +0.019405 + +.. raw:: html + + + +0.146291 + +.. raw:: html + +
+ +2015-03-14 23:59:00+00:00 + +.. raw:: html + + + +0.565911 + +.. raw:: html + + + +0.019373 + +.. raw:: html + + + +-1.344915 + +.. raw:: html + + + +0.146250 + +.. raw:: html + + + +0.739230 + +.. raw:: html + + + +0.675665 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +87481.800 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +87484.980 + +.. raw:: html + + + +87484.980 + +.. raw:: html + + + +14 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0213 + +.. raw:: html + + + +48310 + +.. raw:: html + + + +0.0213 + +.. raw:: html + + + +0.019373 + +.. raw:: html + + + +0.146250 + +.. raw:: html + +
+ +2015-03-15 23:59:00+00:00 + +.. raw:: html + + + +0.551394 + +.. raw:: html + + + +0.041659 + +.. raw:: html + + + +-1.191436 + +.. raw:: html + + + +0.175450 + +.. raw:: html + + + +0.714876 + +.. raw:: html + + + +0.680484 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +89710.344 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +87481.800 + +.. raw:: html + + + +87481.800 + +.. raw:: html + + + +15 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0213 + +.. raw:: html + + + +29454 + +.. raw:: html + + + +0.0213 + +.. raw:: html + + + +0.041659 + +.. raw:: html + + + +0.175450 + +.. raw:: html + +
+ +2015-03-16 23:59:00+00:00 + +.. raw:: html + + + +0.541846 + +.. raw:: html + + + +0.019055 + +.. raw:: html + + + +-1.188212 + +.. raw:: html + + + +0.145833 + +.. raw:: html + + + +0.706049 + +.. raw:: html + + + +0.680281 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +87450.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +89710.344 + +.. raw:: html + + + +89710.344 + +.. raw:: html + + + +16 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0210 + +.. raw:: html + + + +25564 + +.. raw:: html + + + +0.0210 + +.. raw:: html + + + +0.019055 + +.. raw:: html + + + +0.145833 + +.. raw:: html + +
+ +2015-03-17 23:59:00+00:00 + +.. raw:: html + + + +0.524682 + +.. raw:: html + + + +0.019055 + +.. raw:: html + + + +-1.115149 + +.. raw:: html + + + +0.145833 + +.. raw:: html + + + +0.684599 + +.. raw:: html + + + +0.678870 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +87450.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +87450.000 + +.. raw:: html + + + +87450.000 + +.. raw:: html + + + +17 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0206 + +.. raw:: html + + + +9 + +.. raw:: html + + + +0.0206 + +.. raw:: html + + + +0.019055 + +.. raw:: html + + + +0.145833 + +.. raw:: html + +
+ +2015-03-18 23:59:00+00:00 + +.. raw:: html + + + +0.532621 + +.. raw:: html + + + +-0.021999 + +.. raw:: html + + + +-1.180440 + +.. raw:: html + + + +0.092041 + +.. raw:: html + + + +0.696261 + +.. raw:: html + + + +0.685307 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +83344.620 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +87450.000 + +.. raw:: html + + + +87450.000 + +.. raw:: html + + + +18 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0193 + +.. raw:: html + + + +164911 + +.. raw:: html + + + +0.0193 + +.. raw:: html + + + +-0.021999 + +.. raw:: html + + + +0.092041 + +.. raw:: html + +
+ +2015-03-19 23:59:00+00:00 + +.. raw:: html + + + +0.518811 + +.. raw:: html + + + +-0.013234 + +.. raw:: html + + + +-1.096387 + +.. raw:: html + + + +0.103526 + +.. raw:: html + + + +0.676861 + +.. raw:: html + + + +0.686186 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +84221.028 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +83344.620 + +.. raw:: html + + + +83344.620 + +.. raw:: html + + + +19 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0198 + +.. raw:: html + + + +713904 + +.. raw:: html + + + +0.0198 + +.. raw:: html + + + +-0.013234 + +.. raw:: html + + + +0.103526 + +.. raw:: html + +
+ +2015-03-20 23:59:00+00:00 + +.. raw:: html + + + +0.505168 + +.. raw:: html + + + +-0.017324 + +.. raw:: html + + + +-1.050273 + +.. raw:: html + + + +0.098170 + +.. raw:: html + + + +0.659945 + +.. raw:: html + + + +0.685070 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +83812.080 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +84221.028 + +.. raw:: html + + + +84221.028 + +.. raw:: html + + + +20 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0193 + +.. raw:: html + + + +132725 + +.. raw:: html + + + +0.0193 + +.. raw:: html + + + +-0.017324 + +.. raw:: html + + + +0.098170 + +.. raw:: html + +
+ +2015-03-21 23:59:00+00:00 + +.. raw:: html + + + +0.492384 + +.. raw:: html + + + +-0.018494 + +.. raw:: html + + + +-1.002051 + +.. raw:: html + + + +0.096637 + +.. raw:: html + + + +0.643679 + +.. raw:: html + + + +0.684283 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +83695.056 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +83812.080 + +.. raw:: html + + + +83812.080 + +.. raw:: html + + + +21 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0193 + +.. raw:: html + + + +201155 + +.. raw:: html + + + +0.0193 + +.. raw:: html + + + +-0.018494 + +.. raw:: html + + + +0.096637 + +.. raw:: html + +
+ +2015-03-22 23:59:00+00:00 + +.. raw:: html + + + +0.482998 + +.. raw:: html + + + +-0.004744 + +.. raw:: html + + + +-0.927947 + +.. raw:: html + + + +0.114653 + +.. raw:: html + + + +0.629319 + +.. raw:: html + + + +0.686478 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +85070.088 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +83695.056 + +.. raw:: html + + + +83695.056 + +.. raw:: html + + + +22 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0193 + +.. raw:: html + + + +64378 + +.. raw:: html + + + +0.0193 + +.. raw:: html + + + +-0.004744 + +.. raw:: html + + + +0.114653 + +.. raw:: html + +
+ +2015-03-23 23:59:00+00:00 + +.. raw:: html + + + +0.477523 + +.. raw:: html + + + +-0.026505 + +.. raw:: html + + + +-0.935352 + +.. raw:: html + + + +0.086139 + +.. raw:: html + + + +0.623502 + +.. raw:: html + + + +0.687025 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +82894.014 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +85070.088 + +.. raw:: html + + + +85070.088 + +.. raw:: html + + + +23 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0192 + +.. raw:: html + + + +61850 + +.. raw:: html + + + +0.0192 + +.. raw:: html + + + +-0.026505 + +.. raw:: html + + + +0.086139 + +.. raw:: html + +
+ +2015-03-24 23:59:00+00:00 + +.. raw:: html + + + +0.504086 + +.. raw:: html + + + +-0.084215 + +.. raw:: html + + + +-1.021023 + +.. raw:: html + + + +0.010523 + +.. raw:: html + + + +0.655188 + +.. raw:: html + + + +0.701025 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +77122.950 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +82894.014 + +.. raw:: html + + + +82894.014 + +.. raw:: html + + + +24 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0188 + +.. raw:: html + + + +490180 + +.. raw:: html + + + +0.0188 + +.. raw:: html + + + +-0.084215 + +.. raw:: html + + + +0.010523 + +.. raw:: html + +
+ +2015-03-25 23:59:00+00:00 + +.. raw:: html + + + +0.497690 + +.. raw:: html + + + +-0.068474 + +.. raw:: html + + + +-0.952786 + +.. raw:: html + + + +0.031148 + +.. raw:: html + + + +0.644272 + +.. raw:: html + + + +0.704251 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +78697.050 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +77122.950 + +.. raw:: html + + + +77122.950 + +.. raw:: html + + + +25 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0193 + +.. raw:: html + + + +90862 + +.. raw:: html + + + +0.0193 + +.. raw:: html + + + +-0.068474 + +.. raw:: html + + + +0.031148 + +.. raw:: html + +
+ +2015-03-26 23:59:00+00:00 + +.. raw:: html + + + +0.489730 + +.. raw:: html + + + +-0.084215 + +.. raw:: html + + + +-0.943240 + +.. raw:: html + + + +0.010523 + +.. raw:: html + + + +0.634965 + +.. raw:: html + + + +0.703738 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +77122.950 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +78697.050 + +.. raw:: html + + + +78697.050 + +.. raw:: html + + + +26 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0201 + +.. raw:: html + + + +2299 + +.. raw:: html + + + +0.0201 + +.. raw:: html + + + +-0.084215 + +.. raw:: html + + + +0.010523 + +.. raw:: html + +
+ +2015-03-27 23:59:00+00:00 + +.. raw:: html + + + +0.495916 + +.. raw:: html + + + +-0.049785 + +.. raw:: html + + + +-0.857592 + +.. raw:: html + + + +0.055636 + +.. raw:: html + + + +0.636644 + +.. raw:: html + + + +0.713671 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +80565.936 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +77122.950 + +.. raw:: html + + + +77122.950 + +.. raw:: html + + + +27 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0195 + +.. raw:: html + + + +663 + +.. raw:: html + + + +0.0195 + +.. raw:: html + + + +-0.049785 + +.. raw:: html + + + +0.055636 + +.. raw:: html + +
+ +2015-03-28 23:59:00+00:00 + +.. raw:: html + + + +0.488469 + +.. raw:: html + + + +-0.064490 + +.. raw:: html + + + +-0.848769 + +.. raw:: html + + + +0.036368 + +.. raw:: html + + + +0.627920 + +.. raw:: html + + + +0.713212 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +79095.504 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +80565.936 + +.. raw:: html + + + +80565.936 + +.. raw:: html + + + +28 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0195 + +.. raw:: html + + + +7061 + +.. raw:: html + + + +0.0195 + +.. raw:: html + + + +-0.064490 + +.. raw:: html + + + +0.036368 + +.. raw:: html + +
+ +2015-03-29 23:59:00+00:00 + +.. raw:: html + + + +0.479671 + +.. raw:: html + + + +-0.066903 + +.. raw:: html + + + +-0.822844 + +.. raw:: html + + + +0.033205 + +.. raw:: html + + + +0.616787 + +.. raw:: html + + + +0.712868 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +78854.142 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +79095.504 + +.. raw:: html + + + +79095.504 + +.. raw:: html + + + +29 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0195 + +.. raw:: html + + + +8526 + +.. raw:: html + + + +0.0195 + +.. raw:: html + + + +-0.066903 + +.. raw:: html + + + +0.033205 + +.. raw:: html + +
+ +2015-03-30 23:59:00+00:00 + +.. raw:: html + + + +0.476306 + +.. raw:: html + + + +-0.046605 + +.. raw:: html + + + +-0.769239 + +.. raw:: html + + + +0.059803 + +.. raw:: html + + + +0.610002 + +.. raw:: html + + + +0.716464 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +80883.936 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +78854.142 + +.. raw:: html + + + +78854.142 + +.. raw:: html + + + +30 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0196 + +.. raw:: html + + + +29654 + +.. raw:: html + + + +0.0196 + +.. raw:: html + + + +-0.046605 + +.. raw:: html + + + +0.059803 + +.. raw:: html + +
+ +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + + + +… + +.. raw:: html + +
+ +2017-05-30 23:59:00+00:00 + +.. raw:: html + + + +0.495432 + +.. raw:: html + + + +5.949752 + +.. raw:: html + + + +-0.016611 + +.. raw:: html + + + +7.916664 + +.. raw:: html + + + +0.554369 + +.. raw:: html + + + +0.888883 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +680519.682 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +701826.000 + +.. raw:: html + + + +701826.000 + +.. raw:: html + + + +822 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +40157964723 + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +5.949752 + +.. raw:: html + + + +7.916664 + +.. raw:: html + +
+ +2017-05-31 23:59:00+00:00 + +.. raw:: html + + + +0.495243 + +.. raw:: html + + + +6.102328 + +.. raw:: html + + + +-0.017086 + +.. raw:: html + + + +8.154164 + +.. raw:: html + + + +0.554182 + +.. raw:: html + + + +0.888844 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +695777.322 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +680519.682 + +.. raw:: html + + + +680519.682 + +.. raw:: html + + + +823 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +31098652109 + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +6.102328 + +.. raw:: html + + + +8.154164 + +.. raw:: html + +
+ +2017-06-01 23:59:00+00:00 + +.. raw:: html + + + +0.495836 + +.. raw:: html + + + +6.504967 + +.. raw:: html + + + +-0.014668 + +.. raw:: html + + + +8.644144 + +.. raw:: html + + + +0.554541 + +.. raw:: html + + + +0.889303 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +736041.210 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +695777.322 + +.. raw:: html + + + +695777.322 + +.. raw:: html + + + +824 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +40944880757 + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +6.504967 + +.. raw:: html + + + +8.644144 + +.. raw:: html + +
+ +2017-06-02 23:59:00+00:00 + +.. raw:: html + + + +0.495948 + +.. raw:: html + + + +6.801995 + +.. raw:: html + + + +-0.013641 + +.. raw:: html + + + +9.033331 + +.. raw:: html + + + +0.554581 + +.. raw:: html + + + +0.889440 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +765744.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +736041.210 + +.. raw:: html + + + +736041.210 + +.. raw:: html + + + +825 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0215 + +.. raw:: html + + + +22364557424 + +.. raw:: html + + + +0.0215 + +.. raw:: html + + + +6.801995 + +.. raw:: html + + + +9.033331 + +.. raw:: html + +
+ +2017-06-03 23:59:00+00:00 + +.. raw:: html + + + +0.495729 + +.. raw:: html + + + +6.952409 + +.. raw:: html + + + +-0.013100 + +.. raw:: html + + + +9.230418 + +.. raw:: html + + + +0.554317 + +.. raw:: html + + + +0.889470 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +780785.400 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +765744.000 + +.. raw:: html + + + +765744.000 + +.. raw:: html + + + +826 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0215 + +.. raw:: html + + + +23687278961 + +.. raw:: html + + + +0.0215 + +.. raw:: html + + + +6.952409 + +.. raw:: html + + + +9.230418 + +.. raw:: html + +
+ +2017-06-04 23:59:00+00:00 + +.. raw:: html + + + +0.495450 + +.. raw:: html + + + +7.042244 + +.. raw:: html + + + +-0.012768 + +.. raw:: html + + + +9.348122 + +.. raw:: html + + + +0.553999 + +.. raw:: html + + + +0.889479 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +789768.900 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +780785.400 + +.. raw:: html + + + +780785.400 + +.. raw:: html + + + +827 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0215 + +.. raw:: html + + + +21332021248 + +.. raw:: html + + + +0.0215 + +.. raw:: html + + + +7.042244 + +.. raw:: html + + + +9.348122 + +.. raw:: html + +
+ +2017-06-05 23:59:00+00:00 + +.. raw:: html + + + +0.496148 + +.. raw:: html + + + +7.524987 + +.. raw:: html + + + +-0.011320 + +.. raw:: html + + + +9.980649 + +.. raw:: html + + + +0.554578 + +.. raw:: html + + + +0.889805 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +838043.208 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +789768.900 + +.. raw:: html + + + +789768.900 + +.. raw:: html + + + +828 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0218 + +.. raw:: html + + + +22372229837 + +.. raw:: html + + + +0.0218 + +.. raw:: html + + + +7.524987 + +.. raw:: html + + + +9.980649 + +.. raw:: html + +
+ +2017-06-06 23:59:00+00:00 + +.. raw:: html + + + +0.497592 + +.. raw:: html + + + +8.194835 + +.. raw:: html + + + +-0.009554 + +.. raw:: html + + + +10.858330 + +.. raw:: html + + + +0.555841 + +.. raw:: html + + + +0.890368 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +905028.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +838043.208 + +.. raw:: html + + + +838043.208 + +.. raw:: html + + + +829 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0214 + +.. raw:: html + + + +81923184446 + +.. raw:: html + + + +0.0214 + +.. raw:: html + + + +8.194835 + +.. raw:: html + + + +10.858330 + +.. raw:: html + +
+ +2017-06-07 23:59:00+00:00 + +.. raw:: html + + + +0.498895 + +.. raw:: html + + + +7.557258 + +.. raw:: html + + + +-0.011975 + +.. raw:: html + + + +10.022932 + +.. raw:: html + + + +0.557003 + +.. raw:: html + + + +0.890845 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +841270.272 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +905028.000 + +.. raw:: html + + + +905028.000 + +.. raw:: html + + + +830 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0218 + +.. raw:: html + + + +49070430356 + +.. raw:: html + + + +0.0218 + +.. raw:: html + + + +7.557258 + +.. raw:: html + + + +10.022932 + +.. raw:: html + +
+ +2017-06-08 23:59:00+00:00 + +.. raw:: html + + + +0.499349 + +.. raw:: html + + + +8.010395 + +.. raw:: html + + + +-0.010676 + +.. raw:: html + + + +10.616664 + +.. raw:: html + + + +0.557357 + +.. raw:: html + + + +0.891092 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +886584.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +841270.272 + +.. raw:: html + + + +841270.272 + +.. raw:: html + + + +831 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0219 + +.. raw:: html + + + +34013412940 + +.. raw:: html + + + +0.0219 + +.. raw:: html + + + +8.010395 + +.. raw:: html + + + +10.616664 + +.. raw:: html + +
+ +2017-06-09 23:59:00+00:00 + +.. raw:: html + + + +0.499063 + +.. raw:: html + + + +8.099750 + +.. raw:: html + + + +-0.010386 + +.. raw:: html + + + +10.733746 + +.. raw:: html + + + +0.557033 + +.. raw:: html + + + +0.891098 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +895519.482 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +886584.000 + +.. raw:: html + + + +886584.000 + +.. raw:: html + + + +832 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +25275425996 + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +8.099750 + +.. raw:: html + + + +10.733746 + +.. raw:: html + +
+ +2017-06-10 23:59:00+00:00 + +.. raw:: html + + + +0.498769 + +.. raw:: html + + + +8.086143 + +.. raw:: html + + + +-0.010416 + +.. raw:: html + + + +10.715915 + +.. raw:: html + + + +0.556705 + +.. raw:: html + + + +0.891098 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +894158.760 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +895519.482 + +.. raw:: html + + + +895519.482 + +.. raw:: html + + + +833 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +30620792046 + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +8.086143 + +.. raw:: html + + + +10.715915 + +.. raw:: html + +
+ +2017-06-11 23:59:00+00:00 + +.. raw:: html + + + +0.498971 + +.. raw:: html + + + +8.484533 + +.. raw:: html + + + +-0.009305 + +.. raw:: html + + + +11.237914 + +.. raw:: html + + + +0.556827 + +.. raw:: html + + + +0.891266 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +933997.800 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +894158.760 + +.. raw:: html + + + +894158.760 + +.. raw:: html + + + +834 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +30830678595 + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +8.484533 + +.. raw:: html + + + +11.237914 + +.. raw:: html + +
+ +2017-06-12 23:59:00+00:00 + +.. raw:: html + + + +0.503448 + +.. raw:: html + + + +7.320494 + +.. raw:: html + + + +-0.014065 + +.. raw:: html + + + +9.712706 + +.. raw:: html + + + +0.560936 + +.. raw:: html + + + +0.892695 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +817593.900 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +933997.800 + +.. raw:: html + + + +933997.800 + +.. raw:: html + + + +835 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +88704710635 + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +7.320494 + +.. raw:: html + + + +9.712706 + +.. raw:: html + +
+ +2017-06-13 23:59:00+00:00 + +.. raw:: html + + + +0.503565 + +.. raw:: html + + + +7.656697 + +.. raw:: html + + + +-0.013054 + +.. raw:: html + + + +10.153225 + +.. raw:: html + + + +0.560981 + +.. raw:: html + + + +0.892830 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +851214.132 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +817593.900 + +.. raw:: html + + + +817593.900 + +.. raw:: html + + + +836 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +42251296767 + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +7.656697 + +.. raw:: html + + + +10.153225 + +.. raw:: html + +
+ +2017-06-14 23:59:00+00:00 + +.. raw:: html + + + +0.506845 + +.. raw:: html + + + +6.734516 + +.. raw:: html + + + +-0.016873 + +.. raw:: html + + + +8.944917 + +.. raw:: html + + + +0.563995 + +.. raw:: html + + + +0.893862 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +758996.040 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +851214.132 + +.. raw:: html + + + +851214.132 + +.. raw:: html + + + +837 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0215 + +.. raw:: html + + + +63183088135 + +.. raw:: html + + + +0.0215 + +.. raw:: html + + + +6.734516 + +.. raw:: html + + + +8.944917 + +.. raw:: html + +
+ +2017-06-15 23:59:00+00:00 + +.. raw:: html + + + +0.506562 + +.. raw:: html + + + +6.695367 + +.. raw:: html + + + +-0.016991 + +.. raw:: html + + + +8.893622 + +.. raw:: html + + + +0.563678 + +.. raw:: html + + + +0.893865 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +755081.142 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +758996.040 + +.. raw:: html + + + +758996.040 + +.. raw:: html + + + +838 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0216 + +.. raw:: html + + + +104677533974 + +.. raw:: html + + + +0.0216 + +.. raw:: html + + + +6.695367 + +.. raw:: html + + + +8.893622 + +.. raw:: html + +
+ +2017-06-16 23:59:00+00:00 + +.. raw:: html + + + +0.506404 + +.. raw:: html + + + +6.887855 + +.. raw:: html + + + +-0.016343 + +.. raw:: html + + + +9.145831 + +.. raw:: html + + + +0.563472 + +.. raw:: html + + + +0.893913 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +774330.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +755081.142 + +.. raw:: html + + + +755081.142 + +.. raw:: html + + + +839 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0216 + +.. raw:: html + + + +43479966625 + +.. raw:: html + + + +0.0216 + +.. raw:: html + + + +6.887855 + +.. raw:: html + + + +9.145831 + +.. raw:: html + +
+ +2017-06-17 23:59:00+00:00 + +.. raw:: html + + + +0.507407 + +.. raw:: html + + + +7.435283 + +.. raw:: html + + + +-0.014812 + +.. raw:: html + + + +9.863113 + +.. raw:: html + + + +0.564341 + +.. raw:: html + + + +0.894311 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +829072.746 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +774330.000 + +.. raw:: html + + + +774330.000 + +.. raw:: html + + + +840 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0216 + +.. raw:: html + + + +36800919715 + +.. raw:: html + + + +0.0216 + +.. raw:: html + + + +7.435283 + +.. raw:: html + + + +9.863113 + +.. raw:: html + +
+ +2017-06-18 23:59:00+00:00 + +.. raw:: html + + + +0.507740 + +.. raw:: html + + + +7.070069 + +.. raw:: html + + + +-0.016112 + +.. raw:: html + + + +9.384581 + +.. raw:: html + + + +0.564605 + +.. raw:: html + + + +0.894482 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +792551.400 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +829072.746 + +.. raw:: html + + + +829072.746 + +.. raw:: html + + + +841 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0216 + +.. raw:: html + + + +46411759478 + +.. raw:: html + + + +0.0216 + +.. raw:: html + + + +7.070069 + +.. raw:: html + + + +9.384581 + +.. raw:: html + +
+ +2017-06-19 23:59:00+00:00 + +.. raw:: html + + + +0.507754 + +.. raw:: html + + + +7.358645 + +.. raw:: html + + + +-0.015226 + +.. raw:: html + + + +9.762694 + +.. raw:: html + + + +0.564557 + +.. raw:: html + + + +0.894583 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +821408.946 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +792551.400 + +.. raw:: html + + + +792551.400 + +.. raw:: html + + + +842 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0219 + +.. raw:: html + + + +28294406623 + +.. raw:: html + + + +0.0219 + +.. raw:: html + + + +7.358645 + +.. raw:: html + + + +9.762694 + +.. raw:: html + +
+ +2017-06-20 23:59:00+00:00 + +.. raw:: html + + + +0.507705 + +.. raw:: html + + + +7.628795 + +.. raw:: html + + + +-0.014414 + +.. raw:: html + + + +10.116664 + +.. raw:: html + + + +0.564451 + +.. raw:: html + + + +0.894665 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +848424.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +821408.946 + +.. raw:: html + + + +821408.946 + +.. raw:: html + + + +843 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0216 + +.. raw:: html + + + +36903854052 + +.. raw:: html + + + +0.0216 + +.. raw:: html + + + +7.628795 + +.. raw:: html + + + +10.116664 + +.. raw:: html + +
+ +2017-06-21 23:59:00+00:00 + +.. raw:: html + + + +0.507531 + +.. raw:: html + + + +7.476155 + +.. raw:: html + + + +-0.014900 + +.. raw:: html + + + +9.916664 + +.. raw:: html + + + +0.564238 + +.. raw:: html + + + +0.894696 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +833160.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +848424.000 + +.. raw:: html + + + +848424.000 + +.. raw:: html + + + +844 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0216 + +.. raw:: html + + + +43815656010 + +.. raw:: html + + + +0.0216 + +.. raw:: html + + + +7.476155 + +.. raw:: html + + + +9.916664 + +.. raw:: html + +
+ +2017-06-22 23:59:00+00:00 + +.. raw:: html + + + +0.507315 + +.. raw:: html + + + +7.645891 + +.. raw:: html + + + +-0.014372 + +.. raw:: html + + + +10.139065 + +.. raw:: html + + + +0.563979 + +.. raw:: html + + + +0.894725 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +850133.568 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +833160.000 + +.. raw:: html + + + +833160.000 + +.. raw:: html + + + +845 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0215 + +.. raw:: html + + + +22304647568 + +.. raw:: html + + + +0.0215 + +.. raw:: html + + + +7.645891 + +.. raw:: html + + + +10.139065 + +.. raw:: html + +
+ +2017-06-23 23:59:00+00:00 + +.. raw:: html + + + +0.507020 + +.. raw:: html + + + +7.635155 + +.. raw:: html + + + +-0.014388 + +.. raw:: html + + + +10.124997 + +.. raw:: html + + + +0.563652 + +.. raw:: html + + + +0.894725 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +849060.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +850133.568 + +.. raw:: html + + + +850133.568 + +.. raw:: html + + + +846 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0215 + +.. raw:: html + + + +13090231864 + +.. raw:: html + + + +0.0215 + +.. raw:: html + + + +7.635155 + +.. raw:: html + + + +10.124997 + +.. raw:: html + +
+ +2017-06-24 23:59:00+00:00 + +.. raw:: html + + + +0.507936 + +.. raw:: html + + + +7.105628 + +.. raw:: html + + + +-0.016304 + +.. raw:: html + + + +9.431173 + +.. raw:: html + + + +0.564463 + +.. raw:: html + + + +0.895061 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +796107.276 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +849060.000 + +.. raw:: html + + + +849060.000 + +.. raw:: html + + + +847 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0215 + +.. raw:: html + + + +34088563732 + +.. raw:: html + + + +0.0215 + +.. raw:: html + + + +7.105628 + +.. raw:: html + + + +9.431173 + +.. raw:: html + +
+ +2017-06-25 23:59:00+00:00 + +.. raw:: html + + + +0.507675 + +.. raw:: html + + + +7.036714 + +.. raw:: html + + + +-0.016515 + +.. raw:: html + + + +9.340880 + +.. raw:: html + + + +0.564168 + +.. raw:: html + + + +0.895069 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +789215.898 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +796107.276 + +.. raw:: html + + + +796107.276 + +.. raw:: html + + + +848 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0215 + +.. raw:: html + + + +41560204433 + +.. raw:: html + + + +0.0215 + +.. raw:: html + + + +7.036714 + +.. raw:: html + + + +9.340880 + +.. raw:: html + +
+ +2017-06-26 23:59:00+00:00 + +.. raw:: html + + + +0.507780 + +.. raw:: html + + + +6.761571 + +.. raw:: html + + + +-0.017485 + +.. raw:: html + + + +8.980368 + +.. raw:: html + + + +0.564221 + +.. raw:: html + + + +0.895175 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +761701.584 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +789215.898 + +.. raw:: html + + + +789215.898 + +.. raw:: html + + + +849 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0214 + +.. raw:: html + + + +73840480752 + +.. raw:: html + + + +0.0214 + +.. raw:: html + + + +6.761571 + +.. raw:: html + + + +8.980368 + +.. raw:: html + +
+ +2017-06-27 23:59:00+00:00 + +.. raw:: html + + + +0.508048 + +.. raw:: html + + + +7.126355 + +.. raw:: html + + + +-0.016390 + +.. raw:: html + + + +9.458331 + +.. raw:: html + + + +0.564409 + +.. raw:: html + + + +0.895349 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +798180.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +761701.584 + +.. raw:: html + + + +761701.584 + +.. raw:: html + + + +850 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +62426319778 + +.. raw:: html + + + +0.0221 + +.. raw:: html + + + +7.126355 + +.. raw:: html + + + +9.458331 + +.. raw:: html + +
+ +2017-06-28 23:59:00+00:00 + +.. raw:: html + + + +0.507750 + +.. raw:: html + + + +7.135895 + +.. raw:: html + + + +-0.016340 + +.. raw:: html + + + +9.470831 + +.. raw:: html + + + +0.564078 + +.. raw:: html + + + +0.895349 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +799134.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +798180.000 + +.. raw:: html + + + +798180.000 + +.. raw:: html + + + +851 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0222 + +.. raw:: html + + + +39676839183 + +.. raw:: html + + + +0.0222 + +.. raw:: html + + + +7.135895 + +.. raw:: html + + + +9.470831 + +.. raw:: html + +
+ +.. raw:: html + +

+ +851 rows × 45 columns + +.. raw:: html + +

+ +.. raw:: html + +
+ +Also, instead of defining an output file we are accessing it via the “_" +variable that will be created in the name space and contain the +performance DataFrame. + +.. code:: python + + _.head() + +.. raw:: html + +
+ +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + +
+ +.. raw:: html + + + +algo_volatility + +.. raw:: html + + + +algorithm_period_return + +.. raw:: html + + + +alpha + +.. raw:: html + + + +benchmark_period_return + +.. raw:: html + + + +benchmark_volatility + +.. raw:: html + + + +beta + +.. raw:: html + + + +capital_used + +.. raw:: html + + + +cash + +.. raw:: html + + + +ending_cash + +.. raw:: html + + + +ending_exposure + +.. raw:: html + + + +… + +.. raw:: html + + + +starting_cash + +.. raw:: html + + + +starting_exposure + +.. raw:: html + + + +starting_value + +.. raw:: html + + + +trading_days + +.. raw:: html + + + +transactions + +.. raw:: html + + + +treasury_period_return + +.. raw:: html + + + +volume + +.. raw:: html + + + +treasury + +.. raw:: html + + + +algorithm + +.. raw:: html + + + +benchmark + +.. raw:: html + +
+ +2015-03-01 23:59:00+00:00 + +.. raw:: html + + + +NaN + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +NaN + +.. raw:: html + + + +0.045833 + +.. raw:: html + + + +NaN + +.. raw:: html + + + +NaN + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +100000.000000 + +.. raw:: html + + + +100000.000000 + +.. raw:: html + + + +0.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +0.000 + +.. raw:: html + + + +0.000 + +.. raw:: html + + + +1 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0200 + +.. raw:: html + + + +317 + +.. raw:: html + + + +0.0200 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +0.045833 + +.. raw:: html + +
+ +2015-03-02 23:59:00+00:00 + +.. raw:: html + + + +0.000278 + +.. raw:: html + + + +-0.000025 + +.. raw:: html + + + +0.011045 + +.. raw:: html + + + +0.120833 + +.. raw:: html + + + +0.290503 + +.. raw:: html + + + +-0.000956 + +.. raw:: html + + + +-85544.474955 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +85542.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +0.000 + +.. raw:: html + + + +0.000 + +.. raw:: html + + + +2 + +.. raw:: html + + + +[{u’commission’: None, u’amount’: 318, u’sid’:… + +.. raw:: html + + + +0.0208 + +.. raw:: html + + + +98063 + +.. raw:: html + + + +0.0208 + +.. raw:: html + + + +-0.000025 + +.. raw:: html + + + +0.120833 + +.. raw:: html + +
+ +2015-03-03 23:59:00+00:00 + +.. raw:: html + + + +0.051796 + +.. raw:: html + + + +-0.005688 + +.. raw:: html + + + +-1.197544 + +.. raw:: html + + + +0.113416 + +.. raw:: html + + + +0.633538 + +.. raw:: html + + + +0.077239 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +84975.642 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +85542.000 + +.. raw:: html + + + +85542.000 + +.. raw:: html + + + +3 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0212 + +.. raw:: html + + + +442983 + +.. raw:: html + + + +0.0212 + +.. raw:: html + + + +-0.005688 + +.. raw:: html + + + +0.113416 + +.. raw:: html + +
+ +2015-03-04 23:59:00+00:00 + +.. raw:: html + + + +0.342118 + +.. raw:: html + + + +0.034955 + +.. raw:: html + + + +0.401861 + +.. raw:: html + + + +0.166666 + +.. raw:: html + + + +0.524400 + +.. raw:: html + + + +0.181468 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +89040.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +84975.642 + +.. raw:: html + + + +84975.642 + +.. raw:: html + + + +4 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0212 + +.. raw:: html + + + +245889 + +.. raw:: html + + + +0.0212 + +.. raw:: html + + + +0.034955 + +.. raw:: html + + + +0.166666 + +.. raw:: html + +
+ +2015-03-05 23:59:00+00:00 + +.. raw:: html + + + +0.637226 + +.. raw:: html + + + +-0.038185 + +.. raw:: html + + + +-3.914003 + +.. raw:: html + + + +0.070834 + +.. raw:: html + + + +0.976896 + +.. raw:: html + + + +0.550520 + +.. raw:: html + + + +0.000000 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +14455.525045 + +.. raw:: html + + + +81726.000 + +.. raw:: html + + + +… + +.. raw:: html + + + +100000.0 + +.. raw:: html + + + +89040.000 + +.. raw:: html + + + +89040.000 + +.. raw:: html + + + +5 + +.. raw:: html + + + +[] + +.. raw:: html + + + +0.0211 + +.. raw:: html + + + +117440 + +.. raw:: html + + + +0.0211 + +.. raw:: html + + + +-0.038185 + +.. raw:: html + + + +0.070834 + +.. raw:: html + +
+ +.. raw:: html + +

+ +5 rows × 45 columns + +.. raw:: html + +

+ +.. raw:: html + +
+ + + Next steps ~~~~~~~~~~ diff --git a/docs/source/naming-convention.rst b/docs/source/features.rst similarity index 59% rename from docs/source/naming-convention.rst rename to docs/source/features.rst index 2a9e0398..adde8637 100644 --- a/docs/source/naming-convention.rst +++ b/docs/source/features.rst @@ -1,5 +1,56 @@ +Features +======== + +This page describes the features that Catalyst provides in the current version, +and what is planned for future releases. + +Current Functionality +~~~~~~~~~~~~~~~~~~~~~ + +* Backtesting and live-trading modes to run your trading algorithms, with a + seamless transition between the two. +* Support for 3 exchanges: Bitfinex, Bittrex and Poloniex in both modes + (backtesting and live-trading). Historical data for backtesting is provided + with daily resolution for all three exchanges, and minute resolution for + Bitfinex and Poloniex. No minute-resolution data is currently available for + Bittrex. Refer to + `Catalyst Market Coverage `_ for + details. +* Standardized naming convention for all asset pairs trading on any exchange in + the form ``{market_currency}_{base_currency}``. See + :ref:`naming`. +* Output of performance statistics based on Pandas DataFrames to integrate + nicely into the existing PyData ecosystem. +* Support for accessing multiple exchanges per algorithm, which opens the door + to cross-exchange arbitrage opportunities. +* Support for running multiple algorithms on the same exchange independently of + one another. Catalyst performance tracker stores just enough data to allow + algorithms to run independently while still sharing critical data through + exchanges. +* Benchmark defaults to Bitcoin price (btc_usdt in Poloniex exchange) for the + purpose of comparing performance across trading algorithms. A custom benchmark + can be specified through ``set_benchmark()`` (but see + `issue #86 `_). +* Support for MacOS, Linux and Windows installations. +* Support for Python2 and Python3. + +Upcoming features +~~~~~~~~~~~~~~~~~ + +* Paper trading mode (v0.4 - mid Dec. 2017) +* Support for additional exchanges (v0.4 - mid Dec. 2017) +* Additional datasets beyond pricing data (Dec. 2017) +* API documentation (Jan. 2017) +* Support for decentralized exchanges (Jan. 2017) +* Support for data ingestion of community-contributed data sets (Jan. 2017) +* Pipeline support (Jan. 2018) +* Web UI (Q2 2018) + + + .. _naming: + Naming Convention -================= +~~~~~~~~~~~~~~~~~ Catalyst introduces a standardized naming convention for all asset pairs trading on any exchange in the following form: diff --git a/docs/source/index.rst b/docs/source/index.rst index 3e94084f..c500ba29 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -9,9 +9,8 @@ Table of Contents install beginner-tutorial - jupyter live-trading - naming-convention + features example-algos utilities videos @@ -19,7 +18,6 @@ Table of Contents development-guidelines releases .. bundles -.. development-guidelines .. appendix .. release-process diff --git a/docs/source/jupyter.rst b/docs/source/jupyter.rst deleted file mode 100644 index 21fc010a..00000000 --- a/docs/source/jupyter.rst +++ /dev/null @@ -1,15799 +0,0 @@ -Catalyst & Jupyter Notebook -=========================== - -(`This is actual Notebook `_ referenced in the text below) - -The `Jupyter Notebook `__ is a very powerful -browser-based interface to a Python interpreter. As it is already the -de-facto interface for most quantitative researchers, ``catalyst`` -provides an easy way to run your algorithm inside the Notebook without -requiring you to use the CLI. - -Install -^^^^^^^ - -In order to use Jupyter Notebook, you first have to install it inside your -environment. It's available as ``pip`` package, so regardless of how you -installed Catalyst, go inside your catalyst environemnt and run: - -.. code:: bash - - (catalyst)$ pip install jupyter - -Once you have Jupyter Notebook installed, every time you want to use it run: - -.. code:: bash - - (catalyst)$ jupyter notebook - -A local server will launch, and will open a new window on your browser. That's -the interface through which you will interact with Jupyter Notebook. - -Running Algorithms -^^^^^^^^^^^^^^^^^^ - -Before running your algorithms inside the Jupyter Notebook, remember to ingest -the data from the command line interface (CLI). In the example below, you would -need to run first: - -.. code:: bash - - catalyst ingest-exchange -x bitfinex -i btc_usd - -To use Catalyst inside a Jupyter Noebook, you have to write your algorithm in a -cell and let the Jupyter know that it is supposed to execute this algorithm with -Catalyst. This is done via the ``%%catalyst`` IPython magic command that is -available after you import ``catalyst`` from within the Notebook. This magic -takes the same arguments as the command line interface. Thus to run the -algorithm just supply the same parameters as the CLI but without the -f -and -o arguments. We just have to execute the following cell after -importing ``catalyst`` to register the magic. - -.. code:: python - - # Register the catalyst magic - %load_ext catalyst - -.. code:: python - - # Setup matplotlib to display graphs inline in this Notebook - %matplotlib inline - -Note below that we do not have to specify an input file (-f) since the -magic will use the contents of the cell and look for your algorithm -functions. - -.. code:: python - - %%catalyst --start 2015-3-2 --end 2017-6-28 --capital-base 100000 -x bitfinex -c usd - - from catalyst.finance.slippage import VolumeShareSlippage - - from catalyst.api import ( - order_target_value, - symbol, - record, - cancel_order, - get_open_orders, - ) - - def initialize(context): - context.ASSET_NAME = 'btc_usd' - context.TARGET_HODL_RATIO = 0.8 - context.RESERVE_RATIO = 1.0 - context.TARGET_HODL_RATIO - - # For all trading pairs in the poloniex bundle, the default denomination - # currently supported by Catalyst is 1/1000th of a full coin. Use this - # constant to scale the price of up to that of a full coin if desired. - context.TICK_SIZE = 1000.0 - - context.is_buying = True - context.asset = symbol(context.ASSET_NAME) - - context.i = 0 - - def handle_data(context, data): - context.i += 1 - - starting_cash = context.portfolio.starting_cash - target_hodl_value = context.TARGET_HODL_RATIO * starting_cash - reserve_value = context.RESERVE_RATIO * starting_cash - - # Cancel any outstanding orders - orders = get_open_orders(context.asset) or [] - for order in orders: - cancel_order(order) - - # Stop buying after passing the reserve threshold - cash = context.portfolio.cash - if cash <= reserve_value: - context.is_buying = False - - # Retrieve current asset price from pricing data - price = data.current(context.asset, 'price') - - # 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_target_value( - context.asset, - target_hodl_value, - limit_price=price*1.1, - stop_price=price*0.9, - ) - - record( - price=price, - volume=data.current(context.asset, 'volume'), - cash=cash, - starting_cash=context.portfolio.starting_cash, - leverage=context.account.leverage, - ) - - def analyze(context=None, results=None): - import matplotlib.pyplot as plt - - # Plot the portfolio and asset data. - ax1 = plt.subplot(611) - results[['portfolio_value']].plot(ax=ax1) - ax1.set_ylabel('Portfolio Value (USD)') - - ax2 = plt.subplot(612, sharex=ax1) - ax2.set_ylabel('{asset} (USD)'.format(asset=context.ASSET_NAME)) - (context.TICK_SIZE * results[['price']]).plot(ax=ax2) - - trans = results.ix[[t != [] for t in results.transactions]] - buys = trans.ix[ - [t[0]['amount'] > 0 for t in trans.transactions] - ] - ax2.plot( - buys.index, - context.TICK_SIZE * results.price[buys.index], - '^', - markersize=10, - color='g', - ) - - ax3 = plt.subplot(613, sharex=ax1) - results[['leverage', 'alpha', 'beta']].plot(ax=ax3) - ax3.set_ylabel('Leverage ') - - ax4 = plt.subplot(614, sharex=ax1) - results[['starting_cash', 'cash']].plot(ax=ax4) - ax4.set_ylabel('Cash (USD)') - - results[[ - 'treasury', - 'algorithm', - 'benchmark', - ]] = results[[ - 'treasury_period_return', - 'algorithm_period_return', - 'benchmark_period_return', - ]] - - ax5 = plt.subplot(615, sharex=ax1) - results[[ - 'treasury', - 'algorithm', - 'benchmark', - ]].plot(ax=ax5) - ax5.set_ylabel('Percent Change') - - ax6 = plt.subplot(616, sharex=ax1) - results[['volume']].plot(ax=ax6) - ax6.set_ylabel('Volume (mCoins/5min)') - - plt.legend(loc=3) - - # Show the plot. - plt.gcf().set_size_inches(18, 8) - plt.show() - -:: - - [2017-08-11 07:19:46.411748] INFO: Loader: Loading benchmark data for 'USDT_BTC' from 1989-12-31 00:00:00+00:00 to 2017-08-09 00:00:00+00:00 - [2017-08-11 07:19:46.418983] INFO: Loader: Loading data for /Users//.catalyst/data/USDT_BTC_benchmark.csv failed with error [Unknown string format]. - [2017-08-11 07:19:46.419740] INFO: Loader: Cache at /Users//.catalyst/data/USDT_BTC_benchmark.csv does not have data from 1990-01-01 00:00:00+00:00 to 2017-08-09 00:00:00+00:00. - - [2017-08-11 07:19:46.420770] INFO: Loader: Downloading benchmark data for 'USDT_BTC' from 1989-12-31 00:00:00+00:00 to 2017-08-09 00:00:00+00:00 - [2017-08-11 07:19:50.060244] WARNING: Loader: Still don't have expected data after redownload! - [2017-08-11 07:19:50.097334] WARNING: Loader: Refusing to download new treasury data because a download succeeded at 2017-08-11 06:56:49+00:00. - [2017-08-11 07:19:54.618399] INFO: Performance: Simulated 851 trading days out of 851. - [2017-08-11 07:19:54.619301] INFO: Performance: first open: 2015-03-01 00:00:00+00:00 - [2017-08-11 07:19:54.620430] INFO: Performance: last close: 2017-06-28 23:59:00+00:00 - -.. figure:: https://i.imgur.com/DS5w47q.png - :alt: png - -.. raw:: html - -
- -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - -
- -.. raw:: html - - - -algo_volatility - -.. raw:: html - - - -algorithm_period_return - -.. raw:: html - - - -alpha - -.. raw:: html - - - -benchmark_period_return - -.. raw:: html - - - -benchmark_volatility - -.. raw:: html - - - -beta - -.. raw:: html - - - -capital_used - -.. raw:: html - - - -cash - -.. raw:: html - - - -ending_cash - -.. raw:: html - - - -ending_exposure - -.. raw:: html - - - -… - -.. raw:: html - - - -starting_cash - -.. raw:: html - - - -starting_exposure - -.. raw:: html - - - -starting_value - -.. raw:: html - - - -trading_days - -.. raw:: html - - - -transactions - -.. raw:: html - - - -treasury_period_return - -.. raw:: html - - - -volume - -.. raw:: html - - - -treasury - -.. raw:: html - - - -algorithm - -.. raw:: html - - - -benchmark - -.. raw:: html - -
- -2015-03-01 23:59:00+00:00 - -.. raw:: html - - - -NaN - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -NaN - -.. raw:: html - - - -0.045833 - -.. raw:: html - - - -NaN - -.. raw:: html - - - -NaN - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -100000.000000 - -.. raw:: html - - - -100000.000000 - -.. raw:: html - - - -0.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -0.000 - -.. raw:: html - - - -0.000 - -.. raw:: html - - - -1 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0200 - -.. raw:: html - - - -317 - -.. raw:: html - - - -0.0200 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -0.045833 - -.. raw:: html - -
- -2015-03-02 23:59:00+00:00 - -.. raw:: html - - - -0.000278 - -.. raw:: html - - - --0.000025 - -.. raw:: html - - - -0.011045 - -.. raw:: html - - - -0.120833 - -.. raw:: html - - - -0.290503 - -.. raw:: html - - - --0.000956 - -.. raw:: html - - - --85544.474955 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -85542.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -0.000 - -.. raw:: html - - - -0.000 - -.. raw:: html - - - -2 - -.. raw:: html - - - -[{u’commission’: None, u’amount’: 318, u’sid’:… - -.. raw:: html - - - -0.0208 - -.. raw:: html - - - -98063 - -.. raw:: html - - - -0.0208 - -.. raw:: html - - - --0.000025 - -.. raw:: html - - - -0.120833 - -.. raw:: html - -
- -2015-03-03 23:59:00+00:00 - -.. raw:: html - - - -0.051796 - -.. raw:: html - - - --0.005688 - -.. raw:: html - - - --1.197544 - -.. raw:: html - - - -0.113416 - -.. raw:: html - - - -0.633538 - -.. raw:: html - - - -0.077239 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -84975.642 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -85542.000 - -.. raw:: html - - - -85542.000 - -.. raw:: html - - - -3 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0212 - -.. raw:: html - - - -442983 - -.. raw:: html - - - -0.0212 - -.. raw:: html - - - --0.005688 - -.. raw:: html - - - -0.113416 - -.. raw:: html - -
- -2015-03-04 23:59:00+00:00 - -.. raw:: html - - - -0.342118 - -.. raw:: html - - - -0.034955 - -.. raw:: html - - - -0.401861 - -.. raw:: html - - - -0.166666 - -.. raw:: html - - - -0.524400 - -.. raw:: html - - - -0.181468 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -89040.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -84975.642 - -.. raw:: html - - - -84975.642 - -.. raw:: html - - - -4 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0212 - -.. raw:: html - - - -245889 - -.. raw:: html - - - -0.0212 - -.. raw:: html - - - -0.034955 - -.. raw:: html - - - -0.166666 - -.. raw:: html - -
- -2015-03-05 23:59:00+00:00 - -.. raw:: html - - - -0.637226 - -.. raw:: html - - - --0.038185 - -.. raw:: html - - - --3.914003 - -.. raw:: html - - - -0.070834 - -.. raw:: html - - - -0.976896 - -.. raw:: html - - - -0.550520 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -81726.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -89040.000 - -.. raw:: html - - - -89040.000 - -.. raw:: html - - - -5 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0211 - -.. raw:: html - - - -117440 - -.. raw:: html - - - -0.0211 - -.. raw:: html - - - --0.038185 - -.. raw:: html - - - -0.070834 - -.. raw:: html - -
- -2015-03-06 23:59:00+00:00 - -.. raw:: html - - - -0.580521 - -.. raw:: html - - - --0.028645 - -.. raw:: html - - - --3.100822 - -.. raw:: html - - - -0.083333 - -.. raw:: html - - - -0.874082 - -.. raw:: html - - - -0.546703 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -82680.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -81726.000 - -.. raw:: html - - - -81726.000 - -.. raw:: html - - - -6 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0224 - -.. raw:: html - - - -84197 - -.. raw:: html - - - -0.0224 - -.. raw:: html - - - --0.028645 - -.. raw:: html - - - -0.083333 - -.. raw:: html - -
- -2015-03-07 23:59:00+00:00 - -.. raw:: html - - - -0.530557 - -.. raw:: html - - - --0.028645 - -.. raw:: html - - - --2.625704 - -.. raw:: html - - - -0.083333 - -.. raw:: html - - - -0.802793 - -.. raw:: html - - - -0.536589 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -82680.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -82680.000 - -.. raw:: html - - - -82680.000 - -.. raw:: html - - - -7 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0224 - -.. raw:: html - - - -181 - -.. raw:: html - - - -0.0224 - -.. raw:: html - - - --0.028645 - -.. raw:: html - - - -0.083333 - -.. raw:: html - -
- -2015-03-08 23:59:00+00:00 - -.. raw:: html - - - -0.491628 - -.. raw:: html - - - --0.028645 - -.. raw:: html - - - --2.276841 - -.. raw:: html - - - -0.083333 - -.. raw:: html - - - -0.746605 - -.. raw:: html - - - -0.529163 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -82680.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -82680.000 - -.. raw:: html - - - -82680.000 - -.. raw:: html - - - -8 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0224 - -.. raw:: html - - - -30900 - -.. raw:: html - - - -0.0224 - -.. raw:: html - - - --0.028645 - -.. raw:: html - - - -0.083333 - -.. raw:: html - -
- -2015-03-09 23:59:00+00:00 - -.. raw:: html - - - -0.467885 - -.. raw:: html - - - --0.015925 - -.. raw:: html - - - --1.895269 - -.. raw:: html - - - -0.100000 - -.. raw:: html - - - -0.698764 - -.. raw:: html - - - -0.532652 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -83952.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -82680.000 - -.. raw:: html - - - -82680.000 - -.. raw:: html - - - -9 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0220 - -.. raw:: html - - - -128367 - -.. raw:: html - - - -0.0220 - -.. raw:: html - - - --0.015925 - -.. raw:: html - - - -0.100000 - -.. raw:: html - -
- -2015-03-10 23:59:00+00:00 - -.. raw:: html - - - -0.626552 - -.. raw:: html - - - -0.069935 - -.. raw:: html - - - --1.625285 - -.. raw:: html - - - -0.212500 - -.. raw:: html - - - -0.800983 - -.. raw:: html - - - -0.676289 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -92538.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -83952.000 - -.. raw:: html - - - -83952.000 - -.. raw:: html - - - -10 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0214 - -.. raw:: html - - - -54961 - -.. raw:: html - - - -0.0214 - -.. raw:: html - - - -0.069935 - -.. raw:: html - - - -0.212500 - -.. raw:: html - -
- -2015-03-11 23:59:00+00:00 - -.. raw:: html - - - -0.644515 - -.. raw:: html - - - -0.022235 - -.. raw:: html - - - --1.727710 - -.. raw:: html - - - -0.150000 - -.. raw:: html - - - -0.834650 - -.. raw:: html - - - -0.684052 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -87768.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -92538.000 - -.. raw:: html - - - -92538.000 - -.. raw:: html - - - -11 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0211 - -.. raw:: html - - - -42511 - -.. raw:: html - - - -0.0211 - -.. raw:: html - - - -0.022235 - -.. raw:: html - - - -0.150000 - -.. raw:: html - -
- -2015-03-12 23:59:00+00:00 - -.. raw:: html - - - -0.614650 - -.. raw:: html - - - -0.022235 - -.. raw:: html - - - --1.573455 - -.. raw:: html - - - -0.150000 - -.. raw:: html - - - -0.798403 - -.. raw:: html - - - -0.680882 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -87768.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -87768.000 - -.. raw:: html - - - -87768.000 - -.. raw:: html - - - -12 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0210 - -.. raw:: html - - - -2909 - -.. raw:: html - - - -0.0210 - -.. raw:: html - - - -0.022235 - -.. raw:: html - - - -0.150000 - -.. raw:: html - -
- -2015-03-13 23:59:00+00:00 - -.. raw:: html - - - -0.588942 - -.. raw:: html - - - -0.019405 - -.. raw:: html - - - --1.454733 - -.. raw:: html - - - -0.146291 - -.. raw:: html - - - -0.767688 - -.. raw:: html - - - -0.677881 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -87484.980 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -87768.000 - -.. raw:: html - - - -87768.000 - -.. raw:: html - - - -13 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0213 - -.. raw:: html - - - -57613 - -.. raw:: html - - - -0.0213 - -.. raw:: html - - - -0.019405 - -.. raw:: html - - - -0.146291 - -.. raw:: html - -
- -2015-03-14 23:59:00+00:00 - -.. raw:: html - - - -0.565911 - -.. raw:: html - - - -0.019373 - -.. raw:: html - - - --1.344915 - -.. raw:: html - - - -0.146250 - -.. raw:: html - - - -0.739230 - -.. raw:: html - - - -0.675665 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -87481.800 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -87484.980 - -.. raw:: html - - - -87484.980 - -.. raw:: html - - - -14 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0213 - -.. raw:: html - - - -48310 - -.. raw:: html - - - -0.0213 - -.. raw:: html - - - -0.019373 - -.. raw:: html - - - -0.146250 - -.. raw:: html - -
- -2015-03-15 23:59:00+00:00 - -.. raw:: html - - - -0.551394 - -.. raw:: html - - - -0.041659 - -.. raw:: html - - - --1.191436 - -.. raw:: html - - - -0.175450 - -.. raw:: html - - - -0.714876 - -.. raw:: html - - - -0.680484 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -89710.344 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -87481.800 - -.. raw:: html - - - -87481.800 - -.. raw:: html - - - -15 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0213 - -.. raw:: html - - - -29454 - -.. raw:: html - - - -0.0213 - -.. raw:: html - - - -0.041659 - -.. raw:: html - - - -0.175450 - -.. raw:: html - -
- -2015-03-16 23:59:00+00:00 - -.. raw:: html - - - -0.541846 - -.. raw:: html - - - -0.019055 - -.. raw:: html - - - --1.188212 - -.. raw:: html - - - -0.145833 - -.. raw:: html - - - -0.706049 - -.. raw:: html - - - -0.680281 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -87450.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -89710.344 - -.. raw:: html - - - -89710.344 - -.. raw:: html - - - -16 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0210 - -.. raw:: html - - - -25564 - -.. raw:: html - - - -0.0210 - -.. raw:: html - - - -0.019055 - -.. raw:: html - - - -0.145833 - -.. raw:: html - -
- -2015-03-17 23:59:00+00:00 - -.. raw:: html - - - -0.524682 - -.. raw:: html - - - -0.019055 - -.. raw:: html - - - --1.115149 - -.. raw:: html - - - -0.145833 - -.. raw:: html - - - -0.684599 - -.. raw:: html - - - -0.678870 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -87450.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -87450.000 - -.. raw:: html - - - -87450.000 - -.. raw:: html - - - -17 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0206 - -.. raw:: html - - - -9 - -.. raw:: html - - - -0.0206 - -.. raw:: html - - - -0.019055 - -.. raw:: html - - - -0.145833 - -.. raw:: html - -
- -2015-03-18 23:59:00+00:00 - -.. raw:: html - - - -0.532621 - -.. raw:: html - - - --0.021999 - -.. raw:: html - - - --1.180440 - -.. raw:: html - - - -0.092041 - -.. raw:: html - - - -0.696261 - -.. raw:: html - - - -0.685307 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -83344.620 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -87450.000 - -.. raw:: html - - - -87450.000 - -.. raw:: html - - - -18 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0193 - -.. raw:: html - - - -164911 - -.. raw:: html - - - -0.0193 - -.. raw:: html - - - --0.021999 - -.. raw:: html - - - -0.092041 - -.. raw:: html - -
- -2015-03-19 23:59:00+00:00 - -.. raw:: html - - - -0.518811 - -.. raw:: html - - - --0.013234 - -.. raw:: html - - - --1.096387 - -.. raw:: html - - - -0.103526 - -.. raw:: html - - - -0.676861 - -.. raw:: html - - - -0.686186 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -84221.028 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -83344.620 - -.. raw:: html - - - -83344.620 - -.. raw:: html - - - -19 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0198 - -.. raw:: html - - - -713904 - -.. raw:: html - - - -0.0198 - -.. raw:: html - - - --0.013234 - -.. raw:: html - - - -0.103526 - -.. raw:: html - -
- -2015-03-20 23:59:00+00:00 - -.. raw:: html - - - -0.505168 - -.. raw:: html - - - --0.017324 - -.. raw:: html - - - --1.050273 - -.. raw:: html - - - -0.098170 - -.. raw:: html - - - -0.659945 - -.. raw:: html - - - -0.685070 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -83812.080 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -84221.028 - -.. raw:: html - - - -84221.028 - -.. raw:: html - - - -20 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0193 - -.. raw:: html - - - -132725 - -.. raw:: html - - - -0.0193 - -.. raw:: html - - - --0.017324 - -.. raw:: html - - - -0.098170 - -.. raw:: html - -
- -2015-03-21 23:59:00+00:00 - -.. raw:: html - - - -0.492384 - -.. raw:: html - - - --0.018494 - -.. raw:: html - - - --1.002051 - -.. raw:: html - - - -0.096637 - -.. raw:: html - - - -0.643679 - -.. raw:: html - - - -0.684283 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -83695.056 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -83812.080 - -.. raw:: html - - - -83812.080 - -.. raw:: html - - - -21 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0193 - -.. raw:: html - - - -201155 - -.. raw:: html - - - -0.0193 - -.. raw:: html - - - --0.018494 - -.. raw:: html - - - -0.096637 - -.. raw:: html - -
- -2015-03-22 23:59:00+00:00 - -.. raw:: html - - - -0.482998 - -.. raw:: html - - - --0.004744 - -.. raw:: html - - - --0.927947 - -.. raw:: html - - - -0.114653 - -.. raw:: html - - - -0.629319 - -.. raw:: html - - - -0.686478 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -85070.088 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -83695.056 - -.. raw:: html - - - -83695.056 - -.. raw:: html - - - -22 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0193 - -.. raw:: html - - - -64378 - -.. raw:: html - - - -0.0193 - -.. raw:: html - - - --0.004744 - -.. raw:: html - - - -0.114653 - -.. raw:: html - -
- -2015-03-23 23:59:00+00:00 - -.. raw:: html - - - -0.477523 - -.. raw:: html - - - --0.026505 - -.. raw:: html - - - --0.935352 - -.. raw:: html - - - -0.086139 - -.. raw:: html - - - -0.623502 - -.. raw:: html - - - -0.687025 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -82894.014 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -85070.088 - -.. raw:: html - - - -85070.088 - -.. raw:: html - - - -23 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0192 - -.. raw:: html - - - -61850 - -.. raw:: html - - - -0.0192 - -.. raw:: html - - - --0.026505 - -.. raw:: html - - - -0.086139 - -.. raw:: html - -
- -2015-03-24 23:59:00+00:00 - -.. raw:: html - - - -0.504086 - -.. raw:: html - - - --0.084215 - -.. raw:: html - - - --1.021023 - -.. raw:: html - - - -0.010523 - -.. raw:: html - - - -0.655188 - -.. raw:: html - - - -0.701025 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -77122.950 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -82894.014 - -.. raw:: html - - - -82894.014 - -.. raw:: html - - - -24 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0188 - -.. raw:: html - - - -490180 - -.. raw:: html - - - -0.0188 - -.. raw:: html - - - --0.084215 - -.. raw:: html - - - -0.010523 - -.. raw:: html - -
- -2015-03-25 23:59:00+00:00 - -.. raw:: html - - - -0.497690 - -.. raw:: html - - - --0.068474 - -.. raw:: html - - - --0.952786 - -.. raw:: html - - - -0.031148 - -.. raw:: html - - - -0.644272 - -.. raw:: html - - - -0.704251 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -78697.050 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -77122.950 - -.. raw:: html - - - -77122.950 - -.. raw:: html - - - -25 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0193 - -.. raw:: html - - - -90862 - -.. raw:: html - - - -0.0193 - -.. raw:: html - - - --0.068474 - -.. raw:: html - - - -0.031148 - -.. raw:: html - -
- -2015-03-26 23:59:00+00:00 - -.. raw:: html - - - -0.489730 - -.. raw:: html - - - --0.084215 - -.. raw:: html - - - --0.943240 - -.. raw:: html - - - -0.010523 - -.. raw:: html - - - -0.634965 - -.. raw:: html - - - -0.703738 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -77122.950 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -78697.050 - -.. raw:: html - - - -78697.050 - -.. raw:: html - - - -26 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0201 - -.. raw:: html - - - -2299 - -.. raw:: html - - - -0.0201 - -.. raw:: html - - - --0.084215 - -.. raw:: html - - - -0.010523 - -.. raw:: html - -
- -2015-03-27 23:59:00+00:00 - -.. raw:: html - - - -0.495916 - -.. raw:: html - - - --0.049785 - -.. raw:: html - - - --0.857592 - -.. raw:: html - - - -0.055636 - -.. raw:: html - - - -0.636644 - -.. raw:: html - - - -0.713671 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -80565.936 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -77122.950 - -.. raw:: html - - - -77122.950 - -.. raw:: html - - - -27 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0195 - -.. raw:: html - - - -663 - -.. raw:: html - - - -0.0195 - -.. raw:: html - - - --0.049785 - -.. raw:: html - - - -0.055636 - -.. raw:: html - -
- -2015-03-28 23:59:00+00:00 - -.. raw:: html - - - -0.488469 - -.. raw:: html - - - --0.064490 - -.. raw:: html - - - --0.848769 - -.. raw:: html - - - -0.036368 - -.. raw:: html - - - -0.627920 - -.. raw:: html - - - -0.713212 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -79095.504 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -80565.936 - -.. raw:: html - - - -80565.936 - -.. raw:: html - - - -28 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0195 - -.. raw:: html - - - -7061 - -.. raw:: html - - - -0.0195 - -.. raw:: html - - - --0.064490 - -.. raw:: html - - - -0.036368 - -.. raw:: html - -
- -2015-03-29 23:59:00+00:00 - -.. raw:: html - - - -0.479671 - -.. raw:: html - - - --0.066903 - -.. raw:: html - - - --0.822844 - -.. raw:: html - - - -0.033205 - -.. raw:: html - - - -0.616787 - -.. raw:: html - - - -0.712868 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -78854.142 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -79095.504 - -.. raw:: html - - - -79095.504 - -.. raw:: html - - - -29 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0195 - -.. raw:: html - - - -8526 - -.. raw:: html - - - -0.0195 - -.. raw:: html - - - --0.066903 - -.. raw:: html - - - -0.033205 - -.. raw:: html - -
- -2015-03-30 23:59:00+00:00 - -.. raw:: html - - - -0.476306 - -.. raw:: html - - - --0.046605 - -.. raw:: html - - - --0.769239 - -.. raw:: html - - - -0.059803 - -.. raw:: html - - - -0.610002 - -.. raw:: html - - - -0.716464 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -80883.936 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -78854.142 - -.. raw:: html - - - -78854.142 - -.. raw:: html - - - -30 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0196 - -.. raw:: html - - - -29654 - -.. raw:: html - - - -0.0196 - -.. raw:: html - - - --0.046605 - -.. raw:: html - - - -0.059803 - -.. raw:: html - -
- -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - - - -… - -.. raw:: html - -
- -2017-05-30 23:59:00+00:00 - -.. raw:: html - - - -0.495432 - -.. raw:: html - - - -5.949752 - -.. raw:: html - - - --0.016611 - -.. raw:: html - - - -7.916664 - -.. raw:: html - - - -0.554369 - -.. raw:: html - - - -0.888883 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -680519.682 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -701826.000 - -.. raw:: html - - - -701826.000 - -.. raw:: html - - - -822 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -40157964723 - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -5.949752 - -.. raw:: html - - - -7.916664 - -.. raw:: html - -
- -2017-05-31 23:59:00+00:00 - -.. raw:: html - - - -0.495243 - -.. raw:: html - - - -6.102328 - -.. raw:: html - - - --0.017086 - -.. raw:: html - - - -8.154164 - -.. raw:: html - - - -0.554182 - -.. raw:: html - - - -0.888844 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -695777.322 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -680519.682 - -.. raw:: html - - - -680519.682 - -.. raw:: html - - - -823 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -31098652109 - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -6.102328 - -.. raw:: html - - - -8.154164 - -.. raw:: html - -
- -2017-06-01 23:59:00+00:00 - -.. raw:: html - - - -0.495836 - -.. raw:: html - - - -6.504967 - -.. raw:: html - - - --0.014668 - -.. raw:: html - - - -8.644144 - -.. raw:: html - - - -0.554541 - -.. raw:: html - - - -0.889303 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -736041.210 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -695777.322 - -.. raw:: html - - - -695777.322 - -.. raw:: html - - - -824 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -40944880757 - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -6.504967 - -.. raw:: html - - - -8.644144 - -.. raw:: html - -
- -2017-06-02 23:59:00+00:00 - -.. raw:: html - - - -0.495948 - -.. raw:: html - - - -6.801995 - -.. raw:: html - - - --0.013641 - -.. raw:: html - - - -9.033331 - -.. raw:: html - - - -0.554581 - -.. raw:: html - - - -0.889440 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -765744.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -736041.210 - -.. raw:: html - - - -736041.210 - -.. raw:: html - - - -825 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0215 - -.. raw:: html - - - -22364557424 - -.. raw:: html - - - -0.0215 - -.. raw:: html - - - -6.801995 - -.. raw:: html - - - -9.033331 - -.. raw:: html - -
- -2017-06-03 23:59:00+00:00 - -.. raw:: html - - - -0.495729 - -.. raw:: html - - - -6.952409 - -.. raw:: html - - - --0.013100 - -.. raw:: html - - - -9.230418 - -.. raw:: html - - - -0.554317 - -.. raw:: html - - - -0.889470 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -780785.400 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -765744.000 - -.. raw:: html - - - -765744.000 - -.. raw:: html - - - -826 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0215 - -.. raw:: html - - - -23687278961 - -.. raw:: html - - - -0.0215 - -.. raw:: html - - - -6.952409 - -.. raw:: html - - - -9.230418 - -.. raw:: html - -
- -2017-06-04 23:59:00+00:00 - -.. raw:: html - - - -0.495450 - -.. raw:: html - - - -7.042244 - -.. raw:: html - - - --0.012768 - -.. raw:: html - - - -9.348122 - -.. raw:: html - - - -0.553999 - -.. raw:: html - - - -0.889479 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -789768.900 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -780785.400 - -.. raw:: html - - - -780785.400 - -.. raw:: html - - - -827 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0215 - -.. raw:: html - - - -21332021248 - -.. raw:: html - - - -0.0215 - -.. raw:: html - - - -7.042244 - -.. raw:: html - - - -9.348122 - -.. raw:: html - -
- -2017-06-05 23:59:00+00:00 - -.. raw:: html - - - -0.496148 - -.. raw:: html - - - -7.524987 - -.. raw:: html - - - --0.011320 - -.. raw:: html - - - -9.980649 - -.. raw:: html - - - -0.554578 - -.. raw:: html - - - -0.889805 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -838043.208 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -789768.900 - -.. raw:: html - - - -789768.900 - -.. raw:: html - - - -828 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0218 - -.. raw:: html - - - -22372229837 - -.. raw:: html - - - -0.0218 - -.. raw:: html - - - -7.524987 - -.. raw:: html - - - -9.980649 - -.. raw:: html - -
- -2017-06-06 23:59:00+00:00 - -.. raw:: html - - - -0.497592 - -.. raw:: html - - - -8.194835 - -.. raw:: html - - - --0.009554 - -.. raw:: html - - - -10.858330 - -.. raw:: html - - - -0.555841 - -.. raw:: html - - - -0.890368 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -905028.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -838043.208 - -.. raw:: html - - - -838043.208 - -.. raw:: html - - - -829 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0214 - -.. raw:: html - - - -81923184446 - -.. raw:: html - - - -0.0214 - -.. raw:: html - - - -8.194835 - -.. raw:: html - - - -10.858330 - -.. raw:: html - -
- -2017-06-07 23:59:00+00:00 - -.. raw:: html - - - -0.498895 - -.. raw:: html - - - -7.557258 - -.. raw:: html - - - --0.011975 - -.. raw:: html - - - -10.022932 - -.. raw:: html - - - -0.557003 - -.. raw:: html - - - -0.890845 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -841270.272 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -905028.000 - -.. raw:: html - - - -905028.000 - -.. raw:: html - - - -830 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0218 - -.. raw:: html - - - -49070430356 - -.. raw:: html - - - -0.0218 - -.. raw:: html - - - -7.557258 - -.. raw:: html - - - -10.022932 - -.. raw:: html - -
- -2017-06-08 23:59:00+00:00 - -.. raw:: html - - - -0.499349 - -.. raw:: html - - - -8.010395 - -.. raw:: html - - - --0.010676 - -.. raw:: html - - - -10.616664 - -.. raw:: html - - - -0.557357 - -.. raw:: html - - - -0.891092 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -886584.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -841270.272 - -.. raw:: html - - - -841270.272 - -.. raw:: html - - - -831 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0219 - -.. raw:: html - - - -34013412940 - -.. raw:: html - - - -0.0219 - -.. raw:: html - - - -8.010395 - -.. raw:: html - - - -10.616664 - -.. raw:: html - -
- -2017-06-09 23:59:00+00:00 - -.. raw:: html - - - -0.499063 - -.. raw:: html - - - -8.099750 - -.. raw:: html - - - --0.010386 - -.. raw:: html - - - -10.733746 - -.. raw:: html - - - -0.557033 - -.. raw:: html - - - -0.891098 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -895519.482 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -886584.000 - -.. raw:: html - - - -886584.000 - -.. raw:: html - - - -832 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -25275425996 - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -8.099750 - -.. raw:: html - - - -10.733746 - -.. raw:: html - -
- -2017-06-10 23:59:00+00:00 - -.. raw:: html - - - -0.498769 - -.. raw:: html - - - -8.086143 - -.. raw:: html - - - --0.010416 - -.. raw:: html - - - -10.715915 - -.. raw:: html - - - -0.556705 - -.. raw:: html - - - -0.891098 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -894158.760 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -895519.482 - -.. raw:: html - - - -895519.482 - -.. raw:: html - - - -833 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -30620792046 - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -8.086143 - -.. raw:: html - - - -10.715915 - -.. raw:: html - -
- -2017-06-11 23:59:00+00:00 - -.. raw:: html - - - -0.498971 - -.. raw:: html - - - -8.484533 - -.. raw:: html - - - --0.009305 - -.. raw:: html - - - -11.237914 - -.. raw:: html - - - -0.556827 - -.. raw:: html - - - -0.891266 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -933997.800 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -894158.760 - -.. raw:: html - - - -894158.760 - -.. raw:: html - - - -834 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -30830678595 - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -8.484533 - -.. raw:: html - - - -11.237914 - -.. raw:: html - -
- -2017-06-12 23:59:00+00:00 - -.. raw:: html - - - -0.503448 - -.. raw:: html - - - -7.320494 - -.. raw:: html - - - --0.014065 - -.. raw:: html - - - -9.712706 - -.. raw:: html - - - -0.560936 - -.. raw:: html - - - -0.892695 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -817593.900 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -933997.800 - -.. raw:: html - - - -933997.800 - -.. raw:: html - - - -835 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -88704710635 - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -7.320494 - -.. raw:: html - - - -9.712706 - -.. raw:: html - -
- -2017-06-13 23:59:00+00:00 - -.. raw:: html - - - -0.503565 - -.. raw:: html - - - -7.656697 - -.. raw:: html - - - --0.013054 - -.. raw:: html - - - -10.153225 - -.. raw:: html - - - -0.560981 - -.. raw:: html - - - -0.892830 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -851214.132 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -817593.900 - -.. raw:: html - - - -817593.900 - -.. raw:: html - - - -836 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -42251296767 - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -7.656697 - -.. raw:: html - - - -10.153225 - -.. raw:: html - -
- -2017-06-14 23:59:00+00:00 - -.. raw:: html - - - -0.506845 - -.. raw:: html - - - -6.734516 - -.. raw:: html - - - --0.016873 - -.. raw:: html - - - -8.944917 - -.. raw:: html - - - -0.563995 - -.. raw:: html - - - -0.893862 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -758996.040 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -851214.132 - -.. raw:: html - - - -851214.132 - -.. raw:: html - - - -837 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0215 - -.. raw:: html - - - -63183088135 - -.. raw:: html - - - -0.0215 - -.. raw:: html - - - -6.734516 - -.. raw:: html - - - -8.944917 - -.. raw:: html - -
- -2017-06-15 23:59:00+00:00 - -.. raw:: html - - - -0.506562 - -.. raw:: html - - - -6.695367 - -.. raw:: html - - - --0.016991 - -.. raw:: html - - - -8.893622 - -.. raw:: html - - - -0.563678 - -.. raw:: html - - - -0.893865 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -755081.142 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -758996.040 - -.. raw:: html - - - -758996.040 - -.. raw:: html - - - -838 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0216 - -.. raw:: html - - - -104677533974 - -.. raw:: html - - - -0.0216 - -.. raw:: html - - - -6.695367 - -.. raw:: html - - - -8.893622 - -.. raw:: html - -
- -2017-06-16 23:59:00+00:00 - -.. raw:: html - - - -0.506404 - -.. raw:: html - - - -6.887855 - -.. raw:: html - - - --0.016343 - -.. raw:: html - - - -9.145831 - -.. raw:: html - - - -0.563472 - -.. raw:: html - - - -0.893913 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -774330.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -755081.142 - -.. raw:: html - - - -755081.142 - -.. raw:: html - - - -839 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0216 - -.. raw:: html - - - -43479966625 - -.. raw:: html - - - -0.0216 - -.. raw:: html - - - -6.887855 - -.. raw:: html - - - -9.145831 - -.. raw:: html - -
- -2017-06-17 23:59:00+00:00 - -.. raw:: html - - - -0.507407 - -.. raw:: html - - - -7.435283 - -.. raw:: html - - - --0.014812 - -.. raw:: html - - - -9.863113 - -.. raw:: html - - - -0.564341 - -.. raw:: html - - - -0.894311 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -829072.746 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -774330.000 - -.. raw:: html - - - -774330.000 - -.. raw:: html - - - -840 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0216 - -.. raw:: html - - - -36800919715 - -.. raw:: html - - - -0.0216 - -.. raw:: html - - - -7.435283 - -.. raw:: html - - - -9.863113 - -.. raw:: html - -
- -2017-06-18 23:59:00+00:00 - -.. raw:: html - - - -0.507740 - -.. raw:: html - - - -7.070069 - -.. raw:: html - - - --0.016112 - -.. raw:: html - - - -9.384581 - -.. raw:: html - - - -0.564605 - -.. raw:: html - - - -0.894482 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -792551.400 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -829072.746 - -.. raw:: html - - - -829072.746 - -.. raw:: html - - - -841 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0216 - -.. raw:: html - - - -46411759478 - -.. raw:: html - - - -0.0216 - -.. raw:: html - - - -7.070069 - -.. raw:: html - - - -9.384581 - -.. raw:: html - -
- -2017-06-19 23:59:00+00:00 - -.. raw:: html - - - -0.507754 - -.. raw:: html - - - -7.358645 - -.. raw:: html - - - --0.015226 - -.. raw:: html - - - -9.762694 - -.. raw:: html - - - -0.564557 - -.. raw:: html - - - -0.894583 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -821408.946 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -792551.400 - -.. raw:: html - - - -792551.400 - -.. raw:: html - - - -842 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0219 - -.. raw:: html - - - -28294406623 - -.. raw:: html - - - -0.0219 - -.. raw:: html - - - -7.358645 - -.. raw:: html - - - -9.762694 - -.. raw:: html - -
- -2017-06-20 23:59:00+00:00 - -.. raw:: html - - - -0.507705 - -.. raw:: html - - - -7.628795 - -.. raw:: html - - - --0.014414 - -.. raw:: html - - - -10.116664 - -.. raw:: html - - - -0.564451 - -.. raw:: html - - - -0.894665 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -848424.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -821408.946 - -.. raw:: html - - - -821408.946 - -.. raw:: html - - - -843 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0216 - -.. raw:: html - - - -36903854052 - -.. raw:: html - - - -0.0216 - -.. raw:: html - - - -7.628795 - -.. raw:: html - - - -10.116664 - -.. raw:: html - -
- -2017-06-21 23:59:00+00:00 - -.. raw:: html - - - -0.507531 - -.. raw:: html - - - -7.476155 - -.. raw:: html - - - --0.014900 - -.. raw:: html - - - -9.916664 - -.. raw:: html - - - -0.564238 - -.. raw:: html - - - -0.894696 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -833160.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -848424.000 - -.. raw:: html - - - -848424.000 - -.. raw:: html - - - -844 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0216 - -.. raw:: html - - - -43815656010 - -.. raw:: html - - - -0.0216 - -.. raw:: html - - - -7.476155 - -.. raw:: html - - - -9.916664 - -.. raw:: html - -
- -2017-06-22 23:59:00+00:00 - -.. raw:: html - - - -0.507315 - -.. raw:: html - - - -7.645891 - -.. raw:: html - - - --0.014372 - -.. raw:: html - - - -10.139065 - -.. raw:: html - - - -0.563979 - -.. raw:: html - - - -0.894725 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -850133.568 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -833160.000 - -.. raw:: html - - - -833160.000 - -.. raw:: html - - - -845 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0215 - -.. raw:: html - - - -22304647568 - -.. raw:: html - - - -0.0215 - -.. raw:: html - - - -7.645891 - -.. raw:: html - - - -10.139065 - -.. raw:: html - -
- -2017-06-23 23:59:00+00:00 - -.. raw:: html - - - -0.507020 - -.. raw:: html - - - -7.635155 - -.. raw:: html - - - --0.014388 - -.. raw:: html - - - -10.124997 - -.. raw:: html - - - -0.563652 - -.. raw:: html - - - -0.894725 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -849060.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -850133.568 - -.. raw:: html - - - -850133.568 - -.. raw:: html - - - -846 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0215 - -.. raw:: html - - - -13090231864 - -.. raw:: html - - - -0.0215 - -.. raw:: html - - - -7.635155 - -.. raw:: html - - - -10.124997 - -.. raw:: html - -
- -2017-06-24 23:59:00+00:00 - -.. raw:: html - - - -0.507936 - -.. raw:: html - - - -7.105628 - -.. raw:: html - - - --0.016304 - -.. raw:: html - - - -9.431173 - -.. raw:: html - - - -0.564463 - -.. raw:: html - - - -0.895061 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -796107.276 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -849060.000 - -.. raw:: html - - - -849060.000 - -.. raw:: html - - - -847 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0215 - -.. raw:: html - - - -34088563732 - -.. raw:: html - - - -0.0215 - -.. raw:: html - - - -7.105628 - -.. raw:: html - - - -9.431173 - -.. raw:: html - -
- -2017-06-25 23:59:00+00:00 - -.. raw:: html - - - -0.507675 - -.. raw:: html - - - -7.036714 - -.. raw:: html - - - --0.016515 - -.. raw:: html - - - -9.340880 - -.. raw:: html - - - -0.564168 - -.. raw:: html - - - -0.895069 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -789215.898 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -796107.276 - -.. raw:: html - - - -796107.276 - -.. raw:: html - - - -848 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0215 - -.. raw:: html - - - -41560204433 - -.. raw:: html - - - -0.0215 - -.. raw:: html - - - -7.036714 - -.. raw:: html - - - -9.340880 - -.. raw:: html - -
- -2017-06-26 23:59:00+00:00 - -.. raw:: html - - - -0.507780 - -.. raw:: html - - - -6.761571 - -.. raw:: html - - - --0.017485 - -.. raw:: html - - - -8.980368 - -.. raw:: html - - - -0.564221 - -.. raw:: html - - - -0.895175 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -761701.584 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -789215.898 - -.. raw:: html - - - -789215.898 - -.. raw:: html - - - -849 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0214 - -.. raw:: html - - - -73840480752 - -.. raw:: html - - - -0.0214 - -.. raw:: html - - - -6.761571 - -.. raw:: html - - - -8.980368 - -.. raw:: html - -
- -2017-06-27 23:59:00+00:00 - -.. raw:: html - - - -0.508048 - -.. raw:: html - - - -7.126355 - -.. raw:: html - - - --0.016390 - -.. raw:: html - - - -9.458331 - -.. raw:: html - - - -0.564409 - -.. raw:: html - - - -0.895349 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -798180.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -761701.584 - -.. raw:: html - - - -761701.584 - -.. raw:: html - - - -850 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -62426319778 - -.. raw:: html - - - -0.0221 - -.. raw:: html - - - -7.126355 - -.. raw:: html - - - -9.458331 - -.. raw:: html - -
- -2017-06-28 23:59:00+00:00 - -.. raw:: html - - - -0.507750 - -.. raw:: html - - - -7.135895 - -.. raw:: html - - - --0.016340 - -.. raw:: html - - - -9.470831 - -.. raw:: html - - - -0.564078 - -.. raw:: html - - - -0.895349 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -799134.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -798180.000 - -.. raw:: html - - - -798180.000 - -.. raw:: html - - - -851 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0222 - -.. raw:: html - - - -39676839183 - -.. raw:: html - - - -0.0222 - -.. raw:: html - - - -7.135895 - -.. raw:: html - - - -9.470831 - -.. raw:: html - -
- -.. raw:: html - -

- -851 rows × 45 columns - -.. raw:: html - -

- -.. raw:: html - -
- -Also, instead of defining an output file we are accessing it via the “_" -variable that will be created in the name space and contain the -performance DataFrame. - -.. code:: python - - _.head() - -.. raw:: html - -
- -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - -
- -.. raw:: html - - - -algo_volatility - -.. raw:: html - - - -algorithm_period_return - -.. raw:: html - - - -alpha - -.. raw:: html - - - -benchmark_period_return - -.. raw:: html - - - -benchmark_volatility - -.. raw:: html - - - -beta - -.. raw:: html - - - -capital_used - -.. raw:: html - - - -cash - -.. raw:: html - - - -ending_cash - -.. raw:: html - - - -ending_exposure - -.. raw:: html - - - -… - -.. raw:: html - - - -starting_cash - -.. raw:: html - - - -starting_exposure - -.. raw:: html - - - -starting_value - -.. raw:: html - - - -trading_days - -.. raw:: html - - - -transactions - -.. raw:: html - - - -treasury_period_return - -.. raw:: html - - - -volume - -.. raw:: html - - - -treasury - -.. raw:: html - - - -algorithm - -.. raw:: html - - - -benchmark - -.. raw:: html - -
- -2015-03-01 23:59:00+00:00 - -.. raw:: html - - - -NaN - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -NaN - -.. raw:: html - - - -0.045833 - -.. raw:: html - - - -NaN - -.. raw:: html - - - -NaN - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -100000.000000 - -.. raw:: html - - - -100000.000000 - -.. raw:: html - - - -0.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -0.000 - -.. raw:: html - - - -0.000 - -.. raw:: html - - - -1 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0200 - -.. raw:: html - - - -317 - -.. raw:: html - - - -0.0200 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -0.045833 - -.. raw:: html - -
- -2015-03-02 23:59:00+00:00 - -.. raw:: html - - - -0.000278 - -.. raw:: html - - - --0.000025 - -.. raw:: html - - - -0.011045 - -.. raw:: html - - - -0.120833 - -.. raw:: html - - - -0.290503 - -.. raw:: html - - - --0.000956 - -.. raw:: html - - - --85544.474955 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -85542.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -0.000 - -.. raw:: html - - - -0.000 - -.. raw:: html - - - -2 - -.. raw:: html - - - -[{u’commission’: None, u’amount’: 318, u’sid’:… - -.. raw:: html - - - -0.0208 - -.. raw:: html - - - -98063 - -.. raw:: html - - - -0.0208 - -.. raw:: html - - - --0.000025 - -.. raw:: html - - - -0.120833 - -.. raw:: html - -
- -2015-03-03 23:59:00+00:00 - -.. raw:: html - - - -0.051796 - -.. raw:: html - - - --0.005688 - -.. raw:: html - - - --1.197544 - -.. raw:: html - - - -0.113416 - -.. raw:: html - - - -0.633538 - -.. raw:: html - - - -0.077239 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -84975.642 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -85542.000 - -.. raw:: html - - - -85542.000 - -.. raw:: html - - - -3 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0212 - -.. raw:: html - - - -442983 - -.. raw:: html - - - -0.0212 - -.. raw:: html - - - --0.005688 - -.. raw:: html - - - -0.113416 - -.. raw:: html - -
- -2015-03-04 23:59:00+00:00 - -.. raw:: html - - - -0.342118 - -.. raw:: html - - - -0.034955 - -.. raw:: html - - - -0.401861 - -.. raw:: html - - - -0.166666 - -.. raw:: html - - - -0.524400 - -.. raw:: html - - - -0.181468 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -89040.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -84975.642 - -.. raw:: html - - - -84975.642 - -.. raw:: html - - - -4 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0212 - -.. raw:: html - - - -245889 - -.. raw:: html - - - -0.0212 - -.. raw:: html - - - -0.034955 - -.. raw:: html - - - -0.166666 - -.. raw:: html - -
- -2015-03-05 23:59:00+00:00 - -.. raw:: html - - - -0.637226 - -.. raw:: html - - - --0.038185 - -.. raw:: html - - - --3.914003 - -.. raw:: html - - - -0.070834 - -.. raw:: html - - - -0.976896 - -.. raw:: html - - - -0.550520 - -.. raw:: html - - - -0.000000 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -14455.525045 - -.. raw:: html - - - -81726.000 - -.. raw:: html - - - -… - -.. raw:: html - - - -100000.0 - -.. raw:: html - - - -89040.000 - -.. raw:: html - - - -89040.000 - -.. raw:: html - - - -5 - -.. raw:: html - - - -[] - -.. raw:: html - - - -0.0211 - -.. raw:: html - - - -117440 - -.. raw:: html - - - -0.0211 - -.. raw:: html - - - --0.038185 - -.. raw:: html - - - -0.070834 - -.. raw:: html - -
- -.. raw:: html - -

- -5 rows × 45 columns - -.. raw:: html - -

- -.. raw:: html - -
From 025929035e13180fb85c2bddfee43c475525f8ab Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Tue, 12 Dec 2017 09:13:38 -0700 Subject: [PATCH 43/52] DOC: fixed missing link --- docs/source/features.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/features.rst b/docs/source/features.rst index adde8637..3d73a5f4 100644 --- a/docs/source/features.rst +++ b/docs/source/features.rst @@ -34,6 +34,9 @@ Current Functionality * Support for MacOS, Linux and Windows installations. * Support for Python2 and Python3. +For additional details on the functionality added on recent releases, see the +:doc:`Release Notes`. + Upcoming features ~~~~~~~~~~~~~~~~~ From eee8dcbbd6782ef9c70e384fe9abfceb181289ba Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Tue, 12 Dec 2017 13:23:15 -0500 Subject: [PATCH 44/52] DOC: documented paper trading and updated the release notes --- docs/source/live-trading.rst | 6 ++++++ docs/source/releases.rst | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/docs/source/live-trading.rst b/docs/source/live-trading.rst index 7a471d68..2a59cb2d 100644 --- a/docs/source/live-trading.rst +++ b/docs/source/live-trading.rst @@ -106,6 +106,10 @@ What differs are the arguments provided to the catalyst client or Here is the breakdown of the new arguments: - ``live``: Boolean flag which enables live trading. +- ``capital_base``: The amount of base_currency assigned to the strategy. + It has to be lower or equal to the amount of base currency available for + trading on the exchange. For illustration, order_target_percent(asset, 1) + will order the capital_base amount specified here of the specified asset. - ``exchange_name``: The name of the targeted exchange (supported values: *bitfinex*, *bittrex*). - ``algo_namespace``: A arbitrary label assigned to your algorithm for @@ -113,6 +117,8 @@ Here is the breakdown of the new arguments: - ``base_currency``: The base currency used to calculate the statistics of your algorithm. Currently, the base currency of all trading pairs of your algorithm must match this value. +- ``simulate_orders``: Enables the paper trading mode, in which orders are + simulated in Catalyst instead of processed on the exchange. Here is a complete algorithm for reference: `Buy Low and Sell High `_ diff --git a/docs/source/releases.rst b/docs/source/releases.rst index 1e968a57..25ddf153 100644 --- a/docs/source/releases.rst +++ b/docs/source/releases.rst @@ -2,6 +2,15 @@ Release Notes ============= +Version 0.3.10 +^^^^^^^^^^^^^ +**Release Date**: 2017-12-12 + +Bug Fixes +~~~~~~~~~ + +- Fixed issue with fetching assets with daily frequency + Version 0.3.10 ^^^^^^^^^^^^^ **Release Date**: 2017-11-28 @@ -10,6 +19,16 @@ Bug Fixes ~~~~~~~~~ - Fixed issue with fetching assets with daily frequency +- Changed Poloniex interface (should solve :issue:`95` and :issue:`94`) +- Solved issue with overriding commission and slippage (:issue:`87`) +- Fixed inefficiency with Bittrex current prices (:issue:`76`) + +Build +~~~~~ +- Integrated with CCXT +- Added paper trading capability (`simulate_orders=True` param in live mode) +- More granular commissions (:issue:`82`) +- Added market orders in live mode (:issue:`81`) Version 0.3.9 ^^^^^^^^^^^^^ From 021e1fd8c8b8d807224f789e46896244cdeb9e1c Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Tue, 12 Dec 2017 13:28:57 -0500 Subject: [PATCH 45/52] DOC: updating feature list --- docs/source/features.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/source/features.rst b/docs/source/features.rst index 3d73a5f4..c217b86e 100644 --- a/docs/source/features.rst +++ b/docs/source/features.rst @@ -9,6 +9,7 @@ Current Functionality * Backtesting and live-trading modes to run your trading algorithms, with a seamless transition between the two. +* Paper trading simulates order in live-trading mode. * Support for 3 exchanges: Bitfinex, Bittrex and Poloniex in both modes (backtesting and live-trading). Historical data for backtesting is provided with daily resolution for all three exchanges, and minute resolution for @@ -16,6 +17,9 @@ Current Functionality Bittrex. Refer to `Catalyst Market Coverage `_ for details. +* Interface with over 90 exchanges available in live and paper trading modes. +* Granular commission models which closely simulates each exchange fee + structure in backtesting and paper trading. * Standardized naming convention for all asset pairs trading on any exchange in the form ``{market_currency}_{base_currency}``. See :ref:`naming`. @@ -40,8 +44,6 @@ For additional details on the functionality added on recent releases, see the Upcoming features ~~~~~~~~~~~~~~~~~ -* Paper trading mode (v0.4 - mid Dec. 2017) -* Support for additional exchanges (v0.4 - mid Dec. 2017) * Additional datasets beyond pricing data (Dec. 2017) * API documentation (Jan. 2017) * Support for decentralized exchanges (Jan. 2017) From f2e4637f29815a203bbcb060e798a4b215512932 Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Tue, 12 Dec 2017 13:34:18 -0500 Subject: [PATCH 46/52] BUG: trying to mitigate a date adjustment issue which occurs sometimes sometimes in live trading especially with Bitrrex at certain frequencies. --- catalyst/examples/mean_reversion_simple.py | 2 +- catalyst/examples/simple_loop.py | 43 +++++++------ catalyst/examples/simple_universe.py | 2 +- catalyst/exchange/ccxt/ccxt_exchange.py | 18 ++++-- catalyst/exchange/exchange.py | 72 ++++++++++++++++------ catalyst/exchange/exchange_algorithm.py | 18 ++++-- catalyst/exchange/exchange_data_portal.py | 1 + catalyst/exchange/exchange_errors.py | 6 ++ catalyst/utils/run_algo.py | 2 +- 9 files changed, 112 insertions(+), 52 deletions(-) diff --git a/catalyst/examples/mean_reversion_simple.py b/catalyst/examples/mean_reversion_simple.py index b3fb7934..ca03baf0 100644 --- a/catalyst/examples/mean_reversion_simple.py +++ b/catalyst/examples/mean_reversion_simple.py @@ -245,7 +245,7 @@ def analyze(context=None, perf=None): if __name__ == '__main__': # The execution mode: backtest or live - MODE = 'backtest' + MODE = 'live' if MODE == 'backtest': folder = os.path.join( diff --git a/catalyst/examples/simple_loop.py b/catalyst/examples/simple_loop.py index 9aa3b9f1..636940d2 100644 --- a/catalyst/examples/simple_loop.py +++ b/catalyst/examples/simple_loop.py @@ -9,7 +9,7 @@ from catalyst.exchange.stats_utils import get_pretty_stats, \ def initialize(context): print('initializing') - context.asset = symbol('neo_eth') + context.asset = symbol('eth_btc') context.base_price = None @@ -23,8 +23,11 @@ def handle_data(context, data): context.asset, fields='price', bar_count=20, - frequency='15T' + frequency='5T' ) + last_traded = prices.index[-1] + print('last candle date: {}'.format(last_traded)) + rsi = talib.RSI(prices.values, timeperiod=14)[-1] print('got rsi: {}'.format(rsi)) @@ -107,25 +110,27 @@ def analyze(context, perf): pass -run_algorithm( - capital_base=250, - start=pd.to_datetime('2017-11-9 0:00', utc=True), - end=pd.to_datetime('2017-11-10 23:59', utc=True), - data_frequency='minute', - initialize=initialize, - handle_data=handle_data, - analyze=analyze, - exchange_name='bitfinex', - algo_namespace='simple_loop', - base_currency='usd' -) # run_algorithm( +# capital_base=250, +# start=pd.to_datetime('2017-11-9 0:00', utc=True), +# end=pd.to_datetime('2017-11-10 23:59', utc=True), +# data_frequency='minute', # initialize=initialize, # handle_data=handle_data, -# analyze=None, -# exchange_name='binance', -# live=True, +# analyze=analyze, +# exchange_name='bitfinex', # algo_namespace='simple_loop', -# base_currency='eth', -# live_graph=False, +# base_currency='usd' # ) +run_algorithm( + capital_base=1, + initialize=initialize, + handle_data=handle_data, + analyze=None, + exchange_name='binance', + live=True, + algo_namespace='simple_loop', + base_currency='eth', + live_graph=False, + simulate_orders=True +) diff --git a/catalyst/examples/simple_universe.py b/catalyst/examples/simple_universe.py index c27979b9..fec0c340 100644 --- a/catalyst/examples/simple_universe.py +++ b/catalyst/examples/simple_universe.py @@ -163,7 +163,7 @@ if __name__ == '__main__': initialize=initialize, handle_data=handle_data, analyze=analyze, - exchange_name='bitfinex', + exchange_name='poloniex', data_frequency='minute', base_currency='btc', live=False, diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py index 79203b5f..97b29cb1 100644 --- a/catalyst/exchange/ccxt/ccxt_exchange.py +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -4,12 +4,12 @@ from collections import defaultdict import ccxt import pandas as pd import six -from catalyst.assets._assets import TradingPair from ccxt import ExchangeNotAvailable, InvalidOrder from logbook import Logger from six import string_types from catalyst.algorithm import MarketOrder +from catalyst.assets._assets import TradingPair from catalyst.constants import LOG_LEVEL from catalyst.exchange.exchange import Exchange from catalyst.exchange.exchange_bundle import ExchangeBundle @@ -58,8 +58,12 @@ class CCXT(Exchange): self._symbol_maps = [None, None] - markets_symbols = self.api.load_markets() - log.debug('the markets:\n{}'.format(markets_symbols)) + try: + markets_symbols = self.api.load_markets() + log.debug('the markets:\n{}'.format(markets_symbols)) + + except ExchangeNotAvailable as e: + raise ExchangeRequestError(error=e) self.name = exchange_name @@ -185,10 +189,12 @@ class CCXT(Exchange): assets = [assets] symbols = self.get_symbols(assets) - timeframe = self.get_timeframe(freq) - delta = start_dt - get_epoch() - ms = int(delta.total_seconds()) * 1000 + + ms = None + if start_dt is not None: + delta = start_dt - get_epoch() + ms = int(delta.total_seconds()) * 1000 candles = dict() for asset in assets: diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index 3f2ac85a..36d006eb 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -15,7 +15,7 @@ from catalyst.exchange.exchange_bundle import ExchangeBundle from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \ BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \ PricingDataNotLoadedError, \ - NoDataAvailableOnExchange, NoValueForField + NoDataAvailableOnExchange, NoValueForField, LastCandleTooEarlyError from catalyst.exchange.exchange_utils import get_exchange_symbols, \ get_frequency, resample_history_df @@ -176,10 +176,18 @@ class Exchange: assets = [] for symbol in symbols: - asset = self.get_asset( - symbol, data_frequency, is_exchange_symbol, is_local - ) - assets.append(asset) + try: + asset = self.get_asset( + symbol, data_frequency, is_exchange_symbol, is_local + ) + assets.append(asset) + + except SymbolNotFoundOnExchange: + log.debug( + 'skipping non-existent market {} {}'.format( + self.name, symbol + ) + ) return assets def get_asset(self, symbol, data_frequency=None, is_exchange_symbol=False, @@ -227,8 +235,10 @@ class Exchange: elif data_frequency is not None: applies = ( - (data_frequency == 'minute' and a.end_minute is not None) - or (data_frequency == 'daily' and a.end_daily is not None) + ( + data_frequency == 'minute' and a.end_minute is not None) + or ( + data_frequency == 'daily' and a.end_daily is not None) ) else: @@ -441,6 +451,12 @@ class Exchange: Forward-fill missing values. Only has effect if field is 'price'. + Notes + ----- + Catalysts requires an end data with bar count both CCXT wants a + start data with bar count. Since we have to make calculations here, + we ensure that the last candle match the end_dt parameter. + Returns ------- DataFrame @@ -451,6 +467,7 @@ class Exchange: frequency, data_frequency ) adj_bar_count = candle_size * bar_count + start_dt = get_start_dt(end_dt, adj_bar_count, data_frequency) # The get_history method supports multiple asset @@ -459,11 +476,23 @@ class Exchange: assets=assets, bar_count=bar_count, start_dt=start_dt, - end_dt=end_dt + end_dt=end_dt, ) series = dict() for asset in candles: + if end_dt is not None and candles[asset]: + delta = get_delta(candle_size, data_frequency) + adj_end_dt = end_dt - delta + last_candle = candles[asset][-1] + + if last_candle['last_traded'] < adj_end_dt: + raise LastCandleTooEarlyError( + last_traded=last_candle['last_traded'], + end_dt=adj_end_dt, + exchange=self.name, + ) + asset_series = self.get_series_from_candles( candles=candles[asset], start_dt=start_dt, @@ -528,6 +557,7 @@ class Exchange: frequency, data_frequency ) adj_bar_count = candle_size * bar_count + try: series = self.bundle.get_history_window_series_and_load( assets=assets, @@ -537,6 +567,7 @@ class Exchange: data_frequency=data_frequency, force_auto_ingest=force_auto_ingest ) + except (PricingDataNotLoadedError, NoDataAvailableOnExchange): series = dict() @@ -548,7 +579,7 @@ class Exchange: start_dt = get_start_dt(end_dt, adj_bar_count, data_frequency) trailing_dt = \ series[asset].index[-1] + get_delta(1, data_frequency) \ - if asset in series else start_dt + if asset in series else start_dt # The get_history method supports multiple asset # Use the original frequency to let each api optimize @@ -590,24 +621,27 @@ class Exchange: return df - def calculate_totals(self, positions=None): + def calculate_totals(self, check_cash=False, positions=None): """ Update the portfolio cash and position balances based on the latest ticker prices. """ log.debug('synchronizing portfolio with exchange {}'.format(self.name)) - balances = self.get_balances() - cash = balances[self.base_currency]['free'] \ - if self.base_currency in balances else None + cash = None + if check_cash: + balances = self.get_balances() - if cash is None: - raise BaseCurrencyNotFoundError( - base_currency=self.base_currency, - exchange=self.name - ) - log.debug('found base currency balance: {}'.format(cash)) + cash = balances[self.base_currency]['free'] \ + if self.base_currency in balances else None + + if cash is None: + raise BaseCurrencyNotFoundError( + base_currency=self.base_currency, + exchange=self.name + ) + log.debug('found base currency balance: {}'.format(cash)) positions_value = 0.0 if positions: diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index 3505accf..b81a3132 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -498,13 +498,18 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): exchange_positions = \ [positions[asset] for asset in assets] - exchange = self.exchanges[exchange_name] # Type: Exchange - cash, positions_value = \ - exchange.calculate_totals(exchange_positions) + check_cash = (not self.simulate_orders) - total_cash += cash + exchange = self.exchanges[exchange_name] # Type: Exchange + cash, positions_value = exchange.calculate_totals( + positions=exchange_positions, + check_cash=check_cash, + ) total_positions_value += positions_value + if cash is not None: + total_cash += cash + for position in exchange_positions: tracker.update_position( asset=position.asset, @@ -512,7 +517,10 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): last_sale_price=position.last_sale_price ) - if total_cash < self.portfolio.cash: + if cash is None: + total_cash = self.portfolio.cash + + elif total_cash < self.portfolio.cash: raise ValueError('Cash on exchanges is lower than the algo.') return total_cash, total_positions_value diff --git a/catalyst/exchange/exchange_data_portal.py b/catalyst/exchange/exchange_data_portal.py index f5a10a37..f649b3b6 100644 --- a/catalyst/exchange/exchange_data_portal.py +++ b/catalyst/exchange/exchange_data_portal.py @@ -237,6 +237,7 @@ class DataPortalExchangeLive(DataPortalExchangeBase): """ exchange = self.exchanges[exchange_name] + df = exchange.get_history_window( assets, end_dt, diff --git a/catalyst/exchange/exchange_errors.py b/catalyst/exchange/exchange_errors.py index 7744aa44..bb393721 100644 --- a/catalyst/exchange/exchange_errors.py +++ b/catalyst/exchange/exchange_errors.py @@ -263,3 +263,9 @@ class NotEnoughCapitalError(ZiplineError): 'exchange should contain at least as much {base_currency} ' 'as the specified `capital_base`. The current balance {balance} is ' 'lower than the `capital_base`: {capital_base}').strip() + +class LastCandleTooEarlyError(ZiplineError): + msg = ( + 'The trade date of the last candle {last_traded} is before the ' + 'specified end date minus one candle {end_dt}. Please verify how ' + '{exchange} calculates the start date of OHLCV candles.').strip() diff --git a/catalyst/utils/run_algo.py b/catalyst/utils/run_algo.py index a3442ec9..ec20ad85 100644 --- a/catalyst/utils/run_algo.py +++ b/catalyst/utils/run_algo.py @@ -155,7 +155,7 @@ def _run(handle_data, exchanges[exchange_name] = get_exchange( exchange_name=exchange_name, base_currency=base_currency, - must_authenticate=live, + must_authenticate=(live and not simulate_orders), ) open_calendar = get_calendar('OPEN') From a7bcf063c37b977b0bae817708f29fe61ee6dbb4 Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Tue, 12 Dec 2017 13:52:45 -0500 Subject: [PATCH 47/52] BLD: improved stats display in live mode --- catalyst/exchange/exchange_algorithm.py | 1 - catalyst/exchange/stats_utils.py | 19 +++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index b81a3132..cc82b8aa 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -23,7 +23,6 @@ import pandas as pd import catalyst.protocol as zp from catalyst.algorithm import TradingAlgorithm -from catalyst.assets._assets import TradingPair from catalyst.constants import LOG_LEVEL from catalyst.exchange.exchange_blotter import ExchangeBlotter from catalyst.exchange.exchange_errors import ( diff --git a/catalyst/exchange/stats_utils.py b/catalyst/exchange/stats_utils.py index 052f3e2d..faa378ba 100644 --- a/catalyst/exchange/stats_utils.py +++ b/catalyst/exchange/stats_utils.py @@ -242,11 +242,19 @@ def prepare_stats(stats, recorded_cols=list()): lambda transactions: len(transactions) ) - df.set_index(index_cols, drop=True, inplace=True) + if asset_cols: + columns = asset_cols + df.set_index(index_cols, drop=True, inplace=True) + + else: + columns = index_cols + columns.remove('period_close') + df.set_index('period_close', drop=False, inplace=True) + df.dropna(axis=1, how='all', inplace=True) df.sort_index(axis=0, level=0, inplace=True) - return df, asset_cols + return df, columns def get_pretty_stats(stats, recorded_cols=None, num_rows=10): @@ -268,13 +276,8 @@ def get_pretty_stats(stats, recorded_cols=None, num_rows=10): """ if isinstance(stats, pd.DataFrame): - # df = stats - # columns = [ - # 'period_close', 'starting_cash', 'ending_cash', 'portfolio_value', - # 'pnl', 'long_exposure', 'short_exposure', 'orders', 'transactions', - # ] stats = stats.T.to_dict().values() - # else: + df, columns = prepare_stats(stats, recorded_cols=recorded_cols) pd.set_option('display.expand_frame_repr', False) From 7091546b2b471a8077df2d19b3861c7f2991c2f6 Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Tue, 12 Dec 2017 14:19:10 -0500 Subject: [PATCH 48/52] BUG: fixed a standardization issue with historical data in live mode --- catalyst/examples/simple_loop.py | 4 +-- catalyst/exchange/exchange.py | 38 +++++++++++------------ catalyst/exchange/exchange_data_portal.py | 8 ++++- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/catalyst/examples/simple_loop.py b/catalyst/examples/simple_loop.py index 636940d2..f6312096 100644 --- a/catalyst/examples/simple_loop.py +++ b/catalyst/examples/simple_loop.py @@ -23,7 +23,7 @@ def handle_data(context, data): context.asset, fields='price', bar_count=20, - frequency='5T' + frequency='30T' ) last_traded = prices.index[-1] print('last candle date: {}'.format(last_traded)) @@ -127,7 +127,7 @@ run_algorithm( initialize=initialize, handle_data=handle_data, analyze=None, - exchange_name='binance', + exchange_name='gdax', live=True, algo_namespace='simple_loop', base_currency='eth', diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index 36d006eb..cb76b885 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -409,7 +409,7 @@ class Exchange: method='ffill', fill_value=previous_value, ) - + series.sort_index(inplace=True) return series def get_history_window(self, @@ -419,7 +419,7 @@ class Exchange: frequency, field, data_frequency=None, - ffill=True): + is_current=False): """ Public API method that returns a dataframe containing the requested @@ -446,10 +446,9 @@ class Exchange: The frequency of the data to query; i.e. whether the data is 'daily' or 'minute' bars. - # TODO: fill how? - ffill: boolean - Forward-fill missing values. Only has effect if field - is 'price'. + is_current: bool + Skip date filters when current data is requested (last few bars + until now). Notes ----- @@ -475,24 +474,12 @@ class Exchange: freq=freq, assets=assets, bar_count=bar_count, - start_dt=start_dt, - end_dt=end_dt, + start_dt=start_dt if not is_current else None, + end_dt=end_dt if not is_current else None, ) series = dict() for asset in candles: - if end_dt is not None and candles[asset]: - delta = get_delta(candle_size, data_frequency) - adj_end_dt = end_dt - delta - last_candle = candles[asset][-1] - - if last_candle['last_traded'] < adj_end_dt: - raise LastCandleTooEarlyError( - last_traded=last_candle['last_traded'], - end_dt=adj_end_dt, - exchange=self.name, - ) - asset_series = self.get_series_from_candles( candles=candles[asset], start_dt=start_dt, @@ -500,6 +487,17 @@ class Exchange: data_frequency=frequency, field=field, ) + if end_dt is not None: + delta = get_delta(candle_size, data_frequency) + adj_end_dt = end_dt - delta + last_traded = asset_series.index[-1] + + if last_traded < adj_end_dt: + raise LastCandleTooEarlyError( + last_traded=last_traded, + end_dt=adj_end_dt, + exchange=self.name, + ) series[asset] = asset_series df = pd.DataFrame(series) diff --git a/catalyst/exchange/exchange_data_portal.py b/catalyst/exchange/exchange_data_portal.py index f649b3b6..474df98e 100644 --- a/catalyst/exchange/exchange_data_portal.py +++ b/catalyst/exchange/exchange_data_portal.py @@ -238,6 +238,12 @@ class DataPortalExchangeLive(DataPortalExchangeBase): """ exchange = self.exchanges[exchange_name] + if end_dt >= pd.Timestamp.utcnow().floor('1T'): + is_current = True + + else: + is_current = False + df = exchange.get_history_window( assets, end_dt, @@ -245,7 +251,7 @@ class DataPortalExchangeLive(DataPortalExchangeBase): frequency, field, data_frequency, - ffill) + is_current) return df def get_exchange_spot_value(self, exchange_name, assets, field, dt, From ddf0c480a030e8a83e63fdc8b66b057b88fb24ce Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Tue, 12 Dec 2017 14:43:06 -0500 Subject: [PATCH 49/52] BLD: testing each sample algo and fixing an issue with data.history --- catalyst/examples/buy_and_hodl.py | 1 - catalyst/examples/buy_btc_simple.py | 17 ++++++++- catalyst/examples/buy_low_sell_high.py | 21 ++++++++++-- catalyst/examples/buy_low_sell_high_live.py | 38 ++++++++------------- catalyst/examples/simple_loop.py | 2 +- catalyst/exchange/exchange_data_portal.py | 8 +---- 6 files changed, 52 insertions(+), 35 deletions(-) diff --git a/catalyst/examples/buy_and_hodl.py b/catalyst/examples/buy_and_hodl.py index 6722665f..af4774ea 100644 --- a/catalyst/examples/buy_and_hodl.py +++ b/catalyst/examples/buy_and_hodl.py @@ -61,7 +61,6 @@ def handle_data(context, data): context.asset, target_hodl_value, limit_price=price * 1.1, - stop_price=price * 0.9, ) record( diff --git a/catalyst/examples/buy_btc_simple.py b/catalyst/examples/buy_btc_simple.py index 52463d58..51c88adb 100644 --- a/catalyst/examples/buy_btc_simple.py +++ b/catalyst/examples/buy_btc_simple.py @@ -21,8 +21,9 @@ To see which assets are available on each exchange, visit: https://www.enigma.co/catalyst/status ''' - +from catalyst import run_algorithm from catalyst.api import order, record, symbol +import pandas as pd def initialize(context): @@ -32,3 +33,17 @@ def initialize(context): def handle_data(context, data): order(context.asset, 1) record(btc=data.current(context.asset, 'price')) + + +if __name__ == '__main__': + run_algorithm( + capital_base=10000, + data_frequency='daily', + initialize=initialize, + handle_data=handle_data, + exchange_name='bitfinex', + algo_namespace='buy_and_hodl', + base_currency='usd', + start=pd.to_datetime('2015-03-01', utc=True), + end=pd.to_datetime('2017-10-31', utc=True), + ) diff --git a/catalyst/examples/buy_low_sell_high.py b/catalyst/examples/buy_low_sell_high.py index b3afb723..f4c53973 100644 --- a/catalyst/examples/buy_low_sell_high.py +++ b/catalyst/examples/buy_low_sell_high.py @@ -13,6 +13,7 @@ instructions on how to install the required dependencies. import talib from logbook import Logger +from catalyst import run_algorithm from catalyst.api import ( order, order_target_percent, @@ -21,6 +22,7 @@ from catalyst.api import ( get_open_orders, ) from catalyst.exchange.stats_utils import get_pretty_stats +import pandas as pd algo_namespace = 'buy_low_sell_high_xrp' log = Logger(algo_namespace) @@ -101,8 +103,8 @@ def _handle_data(context, data): if price < cost_basis: is_buy = True - elif(position.amount > 0 - and price > cost_basis * (1 + context.PROFIT_TARGET)): + elif (position.amount > 0 + and price > cost_basis * (1 + context.PROFIT_TARGET)): profit = (price * position.amount) - (cost_basis * position.amount) log.info('closing position, taking profit: {}'.format(profit)) order_target_percent( @@ -157,3 +159,18 @@ def handle_data(context, data): def analyze(context, stats): log.info('the daily stats:\n{}'.format(get_pretty_stats(stats))) pass + + +if __name__ == '__main__': + run_algorithm( + capital_base=10000, + data_frequency='daily', + initialize=initialize, + handle_data=handle_data, + analyze=analyze, + exchange_name='poloniex', + algo_namespace='buy_and_hodl', + base_currency='usd', + start=pd.to_datetime('2015-03-01', utc=True), + end=pd.to_datetime('2017-10-31', utc=True), + ) diff --git a/catalyst/examples/buy_low_sell_high_live.py b/catalyst/examples/buy_low_sell_high_live.py index 422033a6..cfd5854c 100644 --- a/catalyst/examples/buy_low_sell_high_live.py +++ b/catalyst/examples/buy_low_sell_high_live.py @@ -41,7 +41,7 @@ def _handle_data(context, data): context.asset, fields='price', bar_count=20, - frequency='1d' + frequency='1D' ) rsi = talib.RSI(prices.values, timeperiod=14)[-1] log.info('got rsi: {}'.format(rsi)) @@ -88,8 +88,8 @@ def _handle_data(context, data): if price < cost_basis: is_buy = True - elif(position.amount > 0 - and price > cost_basis * (1 + context.PROFIT_TARGET)): + elif (position.amount > 0 + and price > cost_basis * (1 + context.PROFIT_TARGET)): profit = (price * position.amount) - (cost_basis * position.amount) log.info('closing position, taking profit: {}'.format(profit)) order_target_percent( @@ -146,23 +146,15 @@ def analyze(context, stats): pass -run_algorithm( - capital_base=100000, - initialize=initialize, - handle_data=handle_data, - analyze=analyze, - exchange_name='poloniex', - start=pd.to_datetime('2017-5-01', utc=True), - end=pd.to_datetime('2017-10-16', utc=True), - base_currency='usdt', - data_frequency='daily' -) -# run_algorithm( -# initialize=initialize, -# handle_data=handle_data, -# analyze=analyze, -# exchange_name='poloniex', -# live=True, -# algo_namespace=algo_namespace, -# base_currency='btc' -# ) +if __name__ == '__main__': + run_algorithm( + capital_base=0.001, + initialize=initialize, + handle_data=handle_data, + analyze=analyze, + exchange_name='bittrex', + live=True, + algo_namespace=algo_namespace, + base_currency='btc', + simulate_orders=True, + ) diff --git a/catalyst/examples/simple_loop.py b/catalyst/examples/simple_loop.py index f6312096..ce977e0a 100644 --- a/catalyst/examples/simple_loop.py +++ b/catalyst/examples/simple_loop.py @@ -127,7 +127,7 @@ run_algorithm( initialize=initialize, handle_data=handle_data, analyze=None, - exchange_name='gdax', + exchange_name='poloniex', live=True, algo_namespace='simple_loop', base_currency='eth', diff --git a/catalyst/exchange/exchange_data_portal.py b/catalyst/exchange/exchange_data_portal.py index 474df98e..02d88ca0 100644 --- a/catalyst/exchange/exchange_data_portal.py +++ b/catalyst/exchange/exchange_data_portal.py @@ -238,12 +238,6 @@ class DataPortalExchangeLive(DataPortalExchangeBase): """ exchange = self.exchanges[exchange_name] - if end_dt >= pd.Timestamp.utcnow().floor('1T'): - is_current = True - - else: - is_current = False - df = exchange.get_history_window( assets, end_dt, @@ -251,7 +245,7 @@ class DataPortalExchangeLive(DataPortalExchangeBase): frequency, field, data_frequency, - is_current) + False) return df def get_exchange_spot_value(self, exchange_name, assets, field, dt, From d41d9095a162e37b3461f195e11bb2efc8c385ee Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Tue, 12 Dec 2017 15:13:57 -0500 Subject: [PATCH 50/52] BLD: adjusted the example algorithms --- catalyst/examples/buy_low_sell_high_live.py | 2 +- catalyst/examples/dual_vwap.py | 190 -------------------- catalyst/examples/mean_reversion_simple.py | 2 +- catalyst/examples/portfolio_optimization.py | 43 ++--- catalyst/examples/rsi_profit_target.py | 42 ++--- catalyst/examples/simple_loop.py | 37 ++-- 6 files changed, 53 insertions(+), 263 deletions(-) delete mode 100644 catalyst/examples/dual_vwap.py diff --git a/catalyst/examples/buy_low_sell_high_live.py b/catalyst/examples/buy_low_sell_high_live.py index cfd5854c..34b1e5f6 100644 --- a/catalyst/examples/buy_low_sell_high_live.py +++ b/catalyst/examples/buy_low_sell_high_live.py @@ -152,7 +152,7 @@ if __name__ == '__main__': initialize=initialize, handle_data=handle_data, analyze=analyze, - exchange_name='bittrex', + exchange_name='binance', live=True, algo_namespace=algo_namespace, base_currency='btc', diff --git a/catalyst/examples/dual_vwap.py b/catalyst/examples/dual_vwap.py deleted file mode 100644 index 7059b865..00000000 --- a/catalyst/examples/dual_vwap.py +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2017 Enigma MPC, Inc. -# Copyright 2014 Quantopian, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from catalyst.api import ( - order_target_percent, - record, - symbol, - get_open_orders, - set_max_leverage, - schedule_function, - date_rules, - attach_pipeline, - pipeline_output, -) - -from catalyst.pipeline import Pipeline -from catalyst.pipeline.data import CryptoPricing -from catalyst.pipeline.factors.crypto import VWAP - - -def initialize(context): - context.ASSET_NAME = 'USDT_BTC' - context.TARGET_INVESTMENT_RATIO = 0.8 - context.SHORT_WINDOW = 30 - context.LONG_WINDOW = 100 - - # For all trading pairs in the poloniex bundle, the default denomination - # currently supported by Catalyst is 1/1000th of a full coin. Use this - # constant to scale the price of up to that of a full coin if desired. - context.TICK_SIZE = 1000.0 - - context.i = 0 - context.asset = symbol(context.ASSET_NAME) - - set_max_leverage(1.0) - - attach_pipeline(make_pipeline(context), 'vwap_pipeline') - - schedule_function( - rebalance, - time_rules=date_rules.every_minute(), - ) - - -def before_trading_start(context, data): - context.pipeline_data = pipeline_output('vwap_pipeline') - - -def make_pipeline(context): - return Pipeline( - columns={ - 'price': CryptoPricing.open.latest, - 'volume': CryptoPricing.volume.latest, - 'short_mavg': VWAP(window_length=context.SHORT_WINDOW), - 'long_mavg': VWAP(window_length=context.LONG_WINDOW), - } - ) - - -def rebalance(context, data): - context.i += 1 - - # skip first LONG_WINDOW bars to fill windows - if context.i < context.LONG_WINDOW: - return - - # get pipeline data for asset of interest - pipeline_data = context.pipeline_data - pipeline_data = pipeline_data[pipeline_data.index == context.asset].iloc[0] - - # retrieve long and short moving averages from pipeline - short_mavg = pipeline_data.short_mavg - long_mavg = pipeline_data.long_mavg - price = pipeline_data.price - volume = pipeline_data.volume - - # check that order has not already been placed - open_orders = get_open_orders() - if context.asset not in open_orders: - # check that the asset of interest can currently be traded - if data.can_trade(context.asset): - # adjust portfolio based on comparison of long and short vwap - if short_mavg > long_mavg: - order_target_percent( - context.asset, - context.TARGET_INVESTMENT_RATIO, - ) - elif short_mavg < long_mavg: - order_target_percent( - context.asset, - 0.0, - ) - - record( - price=price, - cash=context.portfolio.cash, - leverage=context.account.leverage, - short_mavg=short_mavg, - long_mavg=long_mavg, - volume=volume, - ) - - -def analyze(context=None, results=None): - import matplotlib.pyplot as plt - - # Plot the portfolio and asset data. - ax1 = plt.subplot(611) - results[['portfolio_value']].plot(ax=ax1) - ax1.set_ylabel('Portfolio value (USD)') - - ax2 = plt.subplot(612, sharex=ax1) - ax2.set_ylabel('{asset} (USD)'.format(asset=context.ASSET_NAME)) - (context.TICK_SIZE*results[['price', - 'short_mavg', - 'long_mavg']]).plot(ax=ax2) - - trans = results.ix[[t != [] for t in results.transactions]] - - buys = trans.ix[ - [t[0]['amount'] > 0 for t in trans.transactions] - ] - sells = trans.ix[ - [t[0]['amount'] < 0 for t in trans.transactions] - ] - - ax2.plot( - buys.index, - context.TICK_SIZE * results.price[buys.index], - '^', - markersize=10, - color='g', - ) - ax2.plot( - sells.index, - context.TICK_SIZE * results.price[sells.index], - 'v', - markersize=10, - color='r', - ) - - ax3 = plt.subplot(613, sharex=ax1) - results[['leverage', 'alpha', 'beta']].plot(ax=ax3) - ax3.set_ylabel('Leverage (USD)') - - ax4 = plt.subplot(614, sharex=ax1) - results[['cash']].plot(ax=ax4) - ax4.set_ylabel('Cash (USD)') - - results[[ - 'treasury', - 'algorithm', - 'benchmark', - ]] = results[[ - 'treasury_period_return', - 'algorithm_period_return', - 'benchmark_period_return', - ]] - - ax5 = plt.subplot(615, sharex=ax1) - results[[ - 'treasury', - 'algorithm', - 'benchmark', - ]].plot(ax=ax5) - ax5.set_ylabel('Percent Change') - - ax6 = plt.subplot(616, sharex=ax1) - results[['volume']].plot(ax=ax6) - ax6.set_ylabel('Volume (mBTC/day)') - - plt.legend(loc=3) - - # Show the plot. - plt.gcf().set_size_inches(18, 8) - plt.show() diff --git a/catalyst/examples/mean_reversion_simple.py b/catalyst/examples/mean_reversion_simple.py index ca03baf0..b3fb7934 100644 --- a/catalyst/examples/mean_reversion_simple.py +++ b/catalyst/examples/mean_reversion_simple.py @@ -245,7 +245,7 @@ def analyze(context=None, perf=None): if __name__ == '__main__': # The execution mode: backtest or live - MODE = 'live' + MODE = 'backtest' if MODE == 'backtest': folder = os.path.join( diff --git a/catalyst/examples/portfolio_optimization.py b/catalyst/examples/portfolio_optimization.py index e93b2daf..37f8a55d 100644 --- a/catalyst/examples/portfolio_optimization.py +++ b/catalyst/examples/portfolio_optimization.py @@ -43,14 +43,14 @@ def handle_data(context, data): if context.i == 0 or context.i % context.rebalance_period == 0: n = context.window prices = data.history(context.assets, fields='price', - bar_count=n+1, frequency='1d') + bar_count=n + 1, frequency='1d') pr = np.asmatrix(prices) - t_prices = prices.iloc[1:n+1] + t_prices = prices.iloc[1:n + 1] t_val = t_prices.values tminus_prices = prices.iloc[0:n] tminus_val = tminus_prices.values # Compute daily returns (r) - r = np.asmatrix(t_val/tminus_val-1) + r = np.asmatrix(t_val / tminus_val - 1) # Compute the expected returns of each asset with the average # daily return for the selected time window m = np.asmatrix(np.mean(r, axis=0)) @@ -59,20 +59,20 @@ def handle_data(context, data): # Compute excess returns matrix (xr) xr = r - m # Matrix algebra to get variance-covariance matrix - cov_m = np.dot(np.transpose(xr), xr)/n + cov_m = np.dot(np.transpose(xr), xr) / n # Compute asset correlation matrix (informative only) - corr_m = cov_m/np.dot(np.transpose(stds), stds) + corr_m = cov_m / np.dot(np.transpose(stds), stds) # Define portfolio optimization parameters n_portfolios = 50000 - results_array = np.zeros((3+context.nassets, n_portfolios)) + results_array = np.zeros((3 + context.nassets, n_portfolios)) for p in xrange(n_portfolios): weights = np.random.random(context.nassets) weights /= np.sum(weights) w = np.asmatrix(weights) - p_r = np.sum(np.dot(w, np.transpose(m)))*365 + p_r = np.sum(np.dot(w, np.transpose(m))) * 365 p_std = np.sqrt(np.dot(np.dot(w, cov_m), - np.transpose(w)))*np.sqrt(365) + np.transpose(w))) * np.sqrt(365) # store results in results array results_array[0, p] = p_r @@ -82,13 +82,13 @@ def handle_data(context, data): results_array[2, p] = results_array[0, p] / results_array[1, p] i = 0 for iw in weights: - results_array[3+i, p] = weights[i] + results_array[3 + i, p] = weights[i] i += 1 # convert results array to Pandas DataFrame results_frame = pd.DataFrame(np.transpose(results_array), columns=['r', 'stdev', 'sharpe'] - + context.assets) + + context.assets) # locate position of portfolio with highest Sharpe Ratio max_sharpe_port = results_frame.iloc[results_frame['sharpe'].idxmax()] # locate positon of portfolio with minimum standard deviation @@ -129,20 +129,21 @@ def handle_data(context, data): def analyze(context=None, results=None): # Form DataFrame with selected data data = results[['pr', 'r', 'm', 'stds', 'max_sharpe_port', 'corr_m', - 'portfolio_value']] + 'portfolio_value']] # Save results in CSV file filename = os.path.splitext(os.path.basename(__file__))[0] data.to_csv(filename + '.csv') -# Bitcoin data is available from 2015-3-2. Dates vary for other tokens. -start = datetime(2017, 1, 1, 0, 0, 0, 0, pytz.utc) -end = datetime(2017, 8, 16, 0, 0, 0, 0, pytz.utc) -results = run_algorithm(initialize=initialize, - handle_data=handle_data, - analyze=analyze, - start=start, - end=end, - exchange_name='poloniex', - capital_base=100000, ) +if __name__ == '__main__': + # Bitcoin data is available from 2015-3-2. Dates vary for other tokens. + start = datetime(2017, 1, 1, 0, 0, 0, 0, pytz.utc) + end = datetime(2017, 8, 16, 0, 0, 0, 0, pytz.utc) + results = run_algorithm(initialize=initialize, + handle_data=handle_data, + analyze=analyze, + start=start, + end=end, + exchange_name='poloniex', + capital_base=100000, ) diff --git a/catalyst/examples/rsi_profit_target.py b/catalyst/examples/rsi_profit_target.py index 7b8ac868..a07d63f7 100644 --- a/catalyst/examples/rsi_profit_target.py +++ b/catalyst/examples/rsi_profit_target.py @@ -114,7 +114,7 @@ def _handle_data_rsi_only(context, data): prices = data.history( context.asset, fields='price', - bar_count=17, + bar_count=20, frequency='30T' ) except Exception as e: @@ -156,7 +156,7 @@ def handle_data(context, data): dt = data.current_dt if context.last_bar is None or ( - context.last_bar + timedelta(minutes=15)) <= dt: + context.last_bar + timedelta(minutes=15)) <= dt: context.last_bar = dt else: return @@ -249,27 +249,17 @@ def analyze(context=None, results=None): pass -# run_algorithm( -# initialize=initialize, -# handle_data=handle_data, -# analyze=analyze, -# exchange_name='bittrex', -# live=True, -# algo_namespace=algo_namespace, -# base_currency='btc', -# live_graph=False -# ) - -# Backtest -run_algorithm( - capital_base=0.5, - data_frequency='minute', - initialize=initialize, - handle_data=handle_data, - analyze=analyze, - exchange_name='poloniex', - algo_namespace=algo_namespace, - base_currency='btc', - start=pd.to_datetime('2017-9-1', utc=True), - end=pd.to_datetime('2017-10-1', utc=True), -) +if __name__ == '__main__': + # Backtest + run_algorithm( + capital_base=0.5, + data_frequency='minute', + initialize=initialize, + handle_data=handle_data, + analyze=analyze, + exchange_name='poloniex', + algo_namespace=algo_namespace, + base_currency='btc', + start=pd.to_datetime('2017-9-1', utc=True), + end=pd.to_datetime('2017-10-1', utc=True), + ) diff --git a/catalyst/examples/simple_loop.py b/catalyst/examples/simple_loop.py index ce977e0a..51ea435c 100644 --- a/catalyst/examples/simple_loop.py +++ b/catalyst/examples/simple_loop.py @@ -110,27 +110,16 @@ def analyze(context, perf): pass -# run_algorithm( -# capital_base=250, -# start=pd.to_datetime('2017-11-9 0:00', utc=True), -# end=pd.to_datetime('2017-11-10 23:59', utc=True), -# data_frequency='minute', -# initialize=initialize, -# handle_data=handle_data, -# analyze=analyze, -# exchange_name='bitfinex', -# algo_namespace='simple_loop', -# base_currency='usd' -# ) -run_algorithm( - capital_base=1, - initialize=initialize, - handle_data=handle_data, - analyze=None, - exchange_name='poloniex', - live=True, - algo_namespace='simple_loop', - base_currency='eth', - live_graph=False, - simulate_orders=True -) +if __name__ == '__main__': + run_algorithm( + capital_base=1, + initialize=initialize, + handle_data=handle_data, + analyze=None, + exchange_name='poloniex', + live=True, + algo_namespace='simple_loop', + base_currency='eth', + live_graph=False, + simulate_orders=True + ) From a7c9846245b4493f9b35fe2da6402c1a7ed96def Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Tue, 12 Dec 2017 15:36:57 -0500 Subject: [PATCH 51/52] BLD: updated CLI with new parameters --- catalyst/__main__.py | 53 +++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/catalyst/__main__.py b/catalyst/__main__.py index 539a3c27..7fd27c2e 100644 --- a/catalyst/__main__.py +++ b/catalyst/__main__.py @@ -193,9 +193,7 @@ def ipython_only(option): @click.option( '-x', '--exchange-name', - type=click.Choice({'bitfinex', 'bittrex', 'poloniex'}), - help='The name of the targeted exchange (supported: bitfinex,' - ' bittrex, poloniex).', + help='The name of the targeted exchange.', ) @click.option( '-n', @@ -313,11 +311,11 @@ def catalyst_magic(line, cell=None): '--algotext', cell, '--output', os.devnull, # don't write the results by default ] + ([ - # these options are set when running in line magic mode - # set a non None algo text to use the ipython user_ns - '--algotext', '', - '--local-namespace', - ] if cell is None else []) + line.split(), + # these options are set when running in line magic mode + # set a non None algo text to use the ipython user_ns + '--algotext', '', + '--local-namespace', + ] if cell is None else []) + line.split(), '%s%%catalyst' % ((cell or '') and '%'), # don't use system exit and propogate errors to the caller standalone_mode=False, @@ -337,6 +335,12 @@ def catalyst_magic(line, cell=None): type=click.File('r'), help='The file that contains the algorithm to run.', ) +@click.option( + '--capital-base', + type=float, + show_default=True, + help='The amount of capital (in base_currency) allocated to trading.', +) @click.option( '-t', '--algotext', @@ -375,9 +379,7 @@ def catalyst_magic(line, cell=None): @click.option( '-x', '--exchange-name', - type=click.Choice({'bitfinex', 'bittrex', 'poloniex'}), - help='The name of the targeted exchange (supported: bitfinex,' - ' bittrex, poloniex).', + help='The name of the targeted exchange.', ) @click.option( '-n', @@ -396,9 +398,17 @@ def catalyst_magic(line, cell=None): default=False, help='Display live graph.', ) +@click.option( + '--simulate-orders/--no-simulate-orders', + is_flag=True, + default=True, + help='Simulating orders enable the paper trading mode. No orders will be ' + 'sent to the exchange unless set to false.', +) @click.pass_context def live(ctx, algofile, + capital_base, algotext, define, output, @@ -407,7 +417,8 @@ def live(ctx, exchange_name, algo_namespace, base_currency, - live_graph): + live_graph, + simulate_orders): """Trade live with the given algorithm. """ if (algotext is not None) == (algofile is not None): @@ -432,7 +443,7 @@ def live(ctx, algotext=algotext, defines=define, data_frequency=None, - capital_base=None, + capital_base=capital_base, data=None, bundle=None, bundle_timestamp=None, @@ -446,7 +457,9 @@ def live(ctx, exchange=exchange_name, algo_namespace=algo_namespace, base_currency=base_currency, - live_graph=live_graph + live_graph=live_graph, + simulate_orders=simulate_orders, + stats_output=None, ) if output == '-': @@ -461,9 +474,7 @@ def live(ctx, @click.option( '-x', '--exchange-name', - type=click.Choice({'bitfinex', 'bittrex', 'poloniex'}), - help='The name of the exchange bundle to ingest (supported: bitfinex,' - ' bittrex, poloniex).', + help='The name of the exchange bundle to ingest.', ) @click.option( '-f', @@ -567,9 +578,7 @@ def clean_algo(ctx, algo_namespace): @click.option( '-x', '--exchange-name', - type=click.Choice({'bitfinex', 'bittrex', 'poloniex'}), - help='The name of the exchange bundle to ingest (supported: bitfinex,' - ' bittrex, poloniex).', + help='The name of the exchange bundle to ingest.', ) @click.option( '-f', @@ -608,9 +617,7 @@ def clean_exchange(ctx, exchange_name, data_frequency): @click.option( '-x', '--exchange-name', - type=click.Choice({'bitfinex', 'bittrex', 'poloniex'}), - help='The name of the exchange bundle to ingest (supported: bitfinex,' - ' bittrex, poloniex).', + help='The name of the exchange bundle to ingest.', ) @click.option( '-c', From 3014651ac2403d8ce8371b04fc2edc5beee5f939 Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Tue, 12 Dec 2017 15:44:50 -0500 Subject: [PATCH 52/52] BLD: updated CLI with new parameters --- catalyst/__main__.py | 16 ++++++++++++++-- catalyst/utils/run_algo.py | 3 ++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/catalyst/__main__.py b/catalyst/__main__.py index 7fd27c2e..db37f24d 100644 --- a/catalyst/__main__.py +++ b/catalyst/__main__.py @@ -255,8 +255,9 @@ def run(ctx, ctx.fail("must specify a base currency with '-c' in backtest mode") if capital_base is None: - ctx.fail("must specify a capital base with '--capital-base'" - " in backtest mode") + ctx.fail("must specify a capital base with '--capital-base'") + + click.echo('Running in backtesting mode.') perf = _run( initialize=None, @@ -429,11 +430,22 @@ def live(ctx, if exchange_name is None: ctx.fail("must specify an exchange name '-x'") + if algo_namespace is None: ctx.fail("must specify an algorithm name '-n' in live execution mode") + if base_currency is None: ctx.fail("must specify a base currency '-c' in live execution mode") + if capital_base is None: + ctx.fail("must specify a capital base with '--capital-base'") + + if simulate_orders: + click.echo('Running in paper trading mode.') + + else: + click.echo('Running in live trading mode.') + perf = _run( initialize=None, handle_data=None, diff --git a/catalyst/utils/run_algo.py b/catalyst/utils/run_algo.py index ec20ad85..153e3af6 100644 --- a/catalyst/utils/run_algo.py +++ b/catalyst/utils/run_algo.py @@ -141,7 +141,8 @@ def _run(handle_data, else: click.echo(algotext) - mode = 'live' if live else 'backtest' + mode = 'paper-trading' if simulate_orders else 'live-trading' \ + if live else 'backtest' log.info('running algo in {mode} mode'.format(mode=mode)) exchange_name = exchange