From da17e66961684bcd55d96ad2176a204c3fbf5e1f Mon Sep 17 00:00:00 2001 From: fredfortier Date: Fri, 27 Oct 2017 00:30:26 -0400 Subject: [PATCH 01/24] Fixed major issue with sell orders --- catalyst/exchange/exchange_blotter.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/catalyst/exchange/exchange_blotter.py b/catalyst/exchange/exchange_blotter.py index 2ca2cb88..8fcbf623 100644 --- a/catalyst/exchange/exchange_blotter.py +++ b/catalyst/exchange/exchange_blotter.py @@ -5,7 +5,7 @@ from catalyst.constants import LOG_LEVEL from catalyst.finance.blotter import Blotter from catalyst.finance.commission import CommissionModel from catalyst.finance.slippage import SlippageModel -from catalyst.finance.transaction import Transaction +from catalyst.finance.transaction import Transaction, create_transaction log = Logger('exchange_blotter', level=LOG_LEVEL) @@ -97,13 +97,16 @@ class TradingPairFixedSlippage(SlippageModel): execution_price, execution_volume = self.process_order(data, order) - transaction = Transaction( - asset=order.asset, - amount=abs(execution_volume), - dt=dt, - price=execution_price, - order_id=order.id + transaction = create_transaction( + order, dt, execution_price, execution_volume ) + # transaction = Transaction( + # asset=order.asset, + # amount=abs(execution_volume), + # dt=dt, + # price=execution_price, + # order_id=order.id + # ) self._volume_for_bar += abs(transaction.amount) yield order, transaction From b9579ab4b440157415a62e93c805dbe2c026a5a3 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Fri, 27 Oct 2017 00:53:19 -0400 Subject: [PATCH 02/24] Improved frequency support for data.history() in backtest, standardized class names, improved unit tests and working on new sample algo. --- catalyst/__main__.py | 2 +- catalyst/exchange/bundle_utils.py | 3 - catalyst/exchange/exchange.py | 45 +--- ...al_exchange.py => exchange_data_portal.py} | 14 +- catalyst/exchange/exchange_utils.py | 193 ++++++++++++++++- .../exchange/{init_utils.py => factory.py} | 19 +- catalyst/support/__init__.py | 0 catalyst/support/issue_44.py | 109 ++++++++++ catalyst/utils/run_algo.py | 2 +- conatus_strategies/neo_rsi.py | 197 ++++++++++++++++++ tests/exchange/__init.py | 0 tests/exchange/base.py | 1 - tests/exchange/test_bundle.py | 6 +- tests/exchange/test_data_portal.py | 49 +++-- tests/exchange/test_utils.py | 17 ++ 15 files changed, 571 insertions(+), 86 deletions(-) rename catalyst/exchange/{data_portal_exchange.py => exchange_data_portal.py} (97%) rename catalyst/exchange/{init_utils.py => factory.py} (72%) create mode 100644 catalyst/support/__init__.py create mode 100644 catalyst/support/issue_44.py create mode 100644 conatus_strategies/neo_rsi.py create mode 100644 tests/exchange/__init.py create mode 100644 tests/exchange/test_utils.py diff --git a/catalyst/__main__.py b/catalyst/__main__.py index dd15e6f7..8b7f001b 100644 --- a/catalyst/__main__.py +++ b/catalyst/__main__.py @@ -9,7 +9,7 @@ from six import text_type from catalyst.data import bundles as bundles_module from catalyst.exchange.exchange_bundle import ExchangeBundle -from catalyst.exchange.init_utils import get_exchange +from catalyst.exchange.factory import get_exchange from catalyst.utils.cli import Date, Timestamp from catalyst.utils.run_algo import _run, load_extensions diff --git a/catalyst/exchange/bundle_utils.py b/catalyst/exchange/bundle_utils.py index b1aa1a07..acd2cc4b 100644 --- a/catalyst/exchange/bundle_utils.py +++ b/catalyst/exchange/bundle_utils.py @@ -103,8 +103,6 @@ def get_start_dt(end_dt, bar_count, data_frequency): return start_dt - - def get_month_start_end(dt): """ Returns the first and last day of the month for the specified date. @@ -215,4 +213,3 @@ def find_most_recent_time(bundle_name): return list(most_recent_bundle.keys())[0] else: return None - diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index 31037882..3640bbe3 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -20,7 +20,8 @@ from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \ 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 +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 @@ -393,7 +394,7 @@ class Exchange: ) series = pd.Series(values, index=dates) - #TODO: ensure that this working as expected, if not use fillna + # TODO: ensure that this working as expected, if not use fillna series.reindex(periods, method='ffill', fill_value=previous_value) return series @@ -441,25 +442,9 @@ class Exchange: A dataframe containing the requested data. """ - freq_match = re.match(r'([0-9].*)(m|M|d|D)', frequency, re.M | re.I) - if freq_match: - candle_size = int(freq_match.group(1)) - unit = freq_match.group(2) - - else: - raise InvalidHistoryFrequencyError(frequency) - - if unit.lower() == 'd': - if data_frequency == 'minute': - data_frequency = 'daily' - - elif unit.lower() == 'm': - if data_frequency == 'daily': - data_frequency = 'minute' - - else: - raise InvalidHistoryFrequencyError(frequency) - + candle_size, unit, data_frequency = get_frequency( + frequency, data_frequency + ) adj_bar_count = candle_size * bar_count try: series = self.bundle.get_history_window_series_and_load( @@ -512,23 +497,7 @@ class Exchange: else: series[asset] = candle_series - df = pd.DataFrame(series) - - if candle_size > 1: - if field == 'open': - agg = 'first' - elif field == 'high': - agg = 'max' - elif field == 'low': - agg = 'min' - elif field == 'close': - agg = 'last' - elif field == 'volume': - agg = 'sum' - else: - raise ValueError('Invalid field.') - - df = df.resample('{}T'.format(candle_size)).agg(agg) + df = resample_history_df(pd.DataFrame(series), candle_size, field) return df diff --git a/catalyst/exchange/data_portal_exchange.py b/catalyst/exchange/exchange_data_portal.py similarity index 97% rename from catalyst/exchange/data_portal_exchange.py rename to catalyst/exchange/exchange_data_portal.py index 3eebf3aa..3371c584 100644 --- a/catalyst/exchange/data_portal_exchange.py +++ b/catalyst/exchange/exchange_data_portal.py @@ -26,6 +26,7 @@ from catalyst.exchange.exchange_errors import ( ExchangeRequestError, ExchangeBarDataError, PricingDataNotLoadedError) +from catalyst.exchange.exchange_utils import get_frequency, resample_history_df log = Logger('DataPortalExchange', level=LOG_LEVEL) @@ -300,16 +301,23 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase): :param ffill: :return: """ - bundle = self.exchange_bundles[exchange.name] + + candle_size, unit, data_frequency = get_frequency( + frequency, data_frequency + ) + adj_bar_count = candle_size * bar_count + series = bundle.get_history_window_series_and_load( assets=assets, end_dt=end_dt, - bar_count=bar_count, + bar_count=adj_bar_count, field=field, data_frequency=data_frequency ) - return pd.DataFrame(series) + + df = resample_history_df(pd.DataFrame(series), candle_size, field) + return df def get_exchange_spot_value(self, exchange, assets, field, dt, data_frequency): diff --git a/catalyst/exchange/exchange_utils.py b/catalyst/exchange/exchange_utils.py index 77e0fc56..3bd35223 100644 --- a/catalyst/exchange/exchange_utils.py +++ b/catalyst/exchange/exchange_utils.py @@ -1,6 +1,7 @@ import json import os import pickle +import re from catalyst.assets._assets import TradingPair from six.moves.urllib import request @@ -8,7 +9,8 @@ from datetime import date, datetime import pandas as pd -from catalyst.exchange.exchange_errors import ExchangeSymbolsNotFound +from catalyst.exchange.exchange_errors import ExchangeSymbolsNotFound, \ + InvalidHistoryFrequencyError from catalyst.utils.paths import data_root, ensure_directory, \ last_modified_time @@ -17,6 +19,13 @@ SYMBOLS_URL = 'https://s3.amazonaws.com/enigmaco/catalyst-exchanges/' \ def get_exchange_folder(exchange_name, environ=None): + """ + The root path of an exchange folder. + + :param exchange_name: + :param environ: + :return: + """ if not environ: environ = os.environ @@ -28,11 +37,25 @@ def get_exchange_folder(exchange_name, environ=None): def get_exchange_symbols_filename(exchange_name, environ=None): + """ + The absolute path of the exchange's symbol.json file. + + :param exchange_name: + :param environ: + :return: + """ exchange_folder = get_exchange_folder(exchange_name, environ) return os.path.join(exchange_folder, 'symbols.json') def download_exchange_symbols(exchange_name, environ=None): + """ + Downloads the exchange's symbols.json from the repository. + + :param exchange_name: + :param environ: + :return: response + """ filename = get_exchange_symbols_filename(exchange_name) url = SYMBOLS_URL.format(exchange=exchange_name) response = request.urlretrieve(url=url, filename=filename) @@ -40,6 +63,13 @@ def download_exchange_symbols(exchange_name, environ=None): def get_exchange_symbols(exchange_name, environ=None): + """ + The de-serialized content of the exchange's symbols.json. + + :param exchange_name: + :param environ: + :return: + """ filename = get_exchange_symbols_filename(exchange_name) if not os.path.isfile(filename) or \ @@ -60,11 +90,24 @@ def get_exchange_symbols(exchange_name, environ=None): def get_symbols_string(assets): + """ + A concatenated string of symbols from a list of assets. + + :param assets: + :return: + """ array = [assets] if isinstance(assets, TradingPair) else assets return ', '.join([asset.symbol for asset in array]) def get_exchange_auth(exchange_name, environ=None): + """ + The de-serialized contend of the exchange's auth.json file. + + :param exchange_name: + :param environ: + :return: + """ exchange_folder = get_exchange_folder(exchange_name, environ) filename = os.path.join(exchange_folder, 'auth.json') @@ -81,6 +124,13 @@ def get_exchange_auth(exchange_name, environ=None): def get_algo_folder(algo_name, environ=None): + """ + The algorithm root folder of the algorithm. + + :param algo_name: + :param environ: + :return: + """ if not environ: environ = os.environ @@ -92,6 +142,15 @@ def get_algo_folder(algo_name, environ=None): def get_algo_object(algo_name, key, environ=None, rel_path=None): + """ + The de-serialized object of the algo name and key. + + :param algo_name: + :param key: + :param environ: + :param rel_path: + :return: + """ if algo_name is None: return None @@ -113,6 +172,16 @@ def get_algo_object(algo_name, key, environ=None, rel_path=None): def save_algo_object(algo_name, key, obj, environ=None, rel_path=None): + """ + Serialize and save an object by algo name and key. + + :param algo_name: + :param key: + :param obj: + :param environ: + :param rel_path: + :return: + """ folder = get_algo_folder(algo_name, environ) if rel_path is not None: @@ -125,16 +194,16 @@ def save_algo_object(algo_name, key, obj, environ=None, rel_path=None): pickle.dump(obj, handle, protocol=pickle.HIGHEST_PROTOCOL) -def append_algo_object(algo_name, key, obj, environ=None): - algo_folder = get_algo_folder(algo_name, environ) - filename = os.path.join(algo_folder, key + '.p') - - mode = 'a+b' if os.path.isfile(filename) else 'wb' - with open(filename, mode) as handle: - pickle.dump(obj, handle, protocol=pickle.HIGHEST_PROTOCOL) - - def get_algo_df(algo_name, key, environ=None, rel_path=None): + """ + The de-serialized DataFrame of an algo name and key. + + :param algo_name: + :param key: + :param environ: + :param rel_path: + :return: + """ folder = get_algo_folder(algo_name, environ) if rel_path is not None: @@ -153,6 +222,16 @@ def get_algo_df(algo_name, key, environ=None, rel_path=None): def save_algo_df(algo_name, key, df, environ=None, rel_path=None): + """ + Serialize to csv and save a DataFrame by algo name and key. + + :param algo_name: + :param key: + :param df: + :param environ: + :param rel_path: + :return: + """ folder = get_algo_folder(algo_name, environ) if rel_path is not None: @@ -166,6 +245,13 @@ def save_algo_df(algo_name, key, df, environ=None, rel_path=None): def get_exchange_minute_writer_root(exchange_name, environ=None): + """ + The minute writer folder for the exchange. + + :param exchange_name: + :param environ: + :return: + """ exchange_folder = get_exchange_folder(exchange_name, environ) minute_data_folder = os.path.join(exchange_folder, 'minute_data') @@ -175,6 +261,13 @@ def get_exchange_minute_writer_root(exchange_name, environ=None): def get_exchange_bundles_folder(exchange_name, environ=None): + """ + The temp folder for bundle downloads by algo name. + + :param exchange_name: + :param environ: + :return: + """ exchange_folder = get_exchange_folder(exchange_name, environ) temp_bundles = os.path.join(exchange_folder, 'temp_bundles') @@ -184,8 +277,86 @@ def get_exchange_bundles_folder(exchange_name, environ=None): def perf_serial(obj): - """JSON serializer for objects not serializable by default json code""" + """ + JSON serializer for objects not serializable by default json code + :param obj: + :return: + """ if isinstance(obj, (datetime, date)): return obj.isoformat() + raise TypeError("Type %s not serializable" % type(obj)) + + +def get_common_assets(exchanges): + """ + The assets available in all specified exchanges. + + :param exchanges: + :return: + """ + symbols = [] + for exchange_name in exchanges: + s = [asset.symbol for asset in exchanges[exchange_name].get_assets()] + symbols.append(s) + + inter_symbols = set.intersection(*map(set, symbols)) + + assets = [] + for symbol in inter_symbols: + for exchange_name in exchanges: + asset = exchanges[exchange_name].get_asset(symbol) + assets.append(asset) + + return assets + + +def get_frequency(freq, data_frequency): + if freq == 'daily': + freq = '1d' + elif freq == 'minute': + freq = '1m' + + freq_match = re.match(r'([0-9].*)(m|M|d|D)', freq, re.M | re.I) + if freq_match: + candle_size = int(freq_match.group(1)) + unit = freq_match.group(2) + + else: + raise InvalidHistoryFrequencyError(freq) + + if unit.lower() == 'd': + if data_frequency == 'minute': + data_frequency = 'daily' + + elif unit.lower() == 'm': + if data_frequency == 'daily': + data_frequency = 'minute' + + else: + raise InvalidHistoryFrequencyError(freq) + + return candle_size, unit, data_frequency + + +def resample_history_df(df, candle_size, field): + if candle_size > 1: + if field == 'open': + agg = 'first' + elif field == 'high': + agg = 'max' + elif field == 'low': + agg = 'min' + elif field == 'close': + agg = 'last' + elif field == 'volume': + agg = 'sum' + else: + raise ValueError('Invalid field.') + + # TODO: pad with nan? + return df.resample('{}T'.format(candle_size)).agg(agg) + + else: + return df diff --git a/catalyst/exchange/init_utils.py b/catalyst/exchange/factory.py similarity index 72% rename from catalyst/exchange/init_utils.py rename to catalyst/exchange/factory.py index a37f0441..72c66bd8 100644 --- a/catalyst/exchange/init_utils.py +++ b/catalyst/exchange/factory.py @@ -5,28 +5,39 @@ from catalyst.exchange.exchange_utils import get_exchange_auth from catalyst.exchange.poloniex.poloniex import Poloniex -def get_exchange(exchange_name): +def get_exchange(exchange_name, base_currency=None): exchange_auth = get_exchange_auth(exchange_name) if exchange_name == 'bitfinex': return Bitfinex( key=exchange_auth['key'], secret=exchange_auth['secret'], - base_currency=None, # TODO: make optional at the exchange + base_currency=base_currency, portfolio=None ) + elif exchange_name == 'bittrex': return Bittrex( key=exchange_auth['key'], secret=exchange_auth['secret'], - base_currency=None, + base_currency=base_currency, portfolio=None ) + elif exchange_name == 'poloniex': return Poloniex( key=exchange_auth['key'], secret=exchange_auth['secret'], - base_currency=None, + base_currency=base_currency, portfolio=None ) + else: raise ExchangeNotFoundError(exchange_name=exchange_name) + + +def get_exchanges(exchange_names): + exchanges = dict() + for exchange_name in exchange_names: + exchanges[exchange_name] = get_exchange(exchange_name) + + return exchanges diff --git a/catalyst/support/__init__.py b/catalyst/support/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/catalyst/support/issue_44.py b/catalyst/support/issue_44.py new file mode 100644 index 00000000..b6d1277a --- /dev/null +++ b/catalyst/support/issue_44.py @@ -0,0 +1,109 @@ +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/utils/run_algo.py b/catalyst/utils/run_algo.py index a5293ac5..c12dedc9 100644 --- a/catalyst/utils/run_algo.py +++ b/catalyst/utils/run_algo.py @@ -31,7 +31,7 @@ import catalyst.utils.paths as pth from catalyst.exchange.exchange_algorithm import ExchangeTradingAlgorithmLive, \ ExchangeTradingAlgorithmBacktest -from catalyst.exchange.data_portal_exchange import DataPortalExchangeLive, \ +from catalyst.exchange.exchange_data_portal import DataPortalExchangeLive, \ DataPortalExchangeBacktest from catalyst.exchange.asset_finder_exchange import AssetFinderExchange from catalyst.exchange.exchange_portfolio import ExchangePortfolio diff --git a/conatus_strategies/neo_rsi.py b/conatus_strategies/neo_rsi.py new file mode 100644 index 00000000..f1569d2c --- /dev/null +++ b/conatus_strategies/neo_rsi.py @@ -0,0 +1,197 @@ +import talib +import pandas as pd +from logbook import Logger + +from catalyst.api import ( + order, + order_target_percent, + symbol, + record, + get_open_orders, +) +from catalyst.exchange.stats_utils import get_pretty_stats +from catalyst.utils.run_algo import run_algorithm + +algo_namespace = 'neo_rsi' +log = Logger(algo_namespace) + + +def initialize(context): + log.info('initializing algo') + context.asset = symbol('neo_usd', 'bitfinex') + + context.BUY_SIGNAL = 30 + context.SELL_SIGNAL = 70 + context.SLIPPAGE_ALLOWED = 0.02 + + context.errors = [] + pass + + +def _handle_data(context, data): + dt = data.current_dt + log.info('BAR {}'.format(dt)) + + price = data.current(context.asset, 'close') + log.info('got price {price}'.format(price=price)) + + if price is None: + log.warn('no pricing data') + return + + try: + prices = data.history( + context.asset, + fields='price', + bar_count=15, + frequency='15m' + ) + except Exception as e: + log.warn('historical data not available: '.format(e)) + return + + rsi = talib.RSI(prices.values, timeperiod=14)[-1] + log.info('got rsi: {}'.format(rsi)) + + cash = context.portfolio.cash + log.info('base currency available: {cash}'.format(cash=cash)) + + + orders = get_open_orders(context.asset) + # if len(orders) > 0: + # log.info('skipping bar until all open orders execute') + # return + + if context.asset in context.portfolio.positions: + if rsi >= context.SELL_SIGNAL: + position = context.portfolio.positions[context.asset] + log.info('closing position') + amount = -position.amount + expected_proceeds = -(amount * price) + order( + asset=context.asset, + amount=amount, + limit_price=price * (1 - context.SLIPPAGE_ALLOWED), + ) + else: + if rsi <= context.BUY_SIGNAL: + log.info('opening position') + order( + asset=context.asset, + amount=cash / price, + limit_price=price * (1 + context.SLIPPAGE_ALLOWED), + ) + + volume = data.current(context.asset, 'volume') + record( + price=price, + volume=volume, + cash=cash, + starting_cash=context.portfolio.starting_cash, + leverage=context.account.leverage, + rsi=rsi + ) + + +def handle_data(context, data): + try: + _handle_data(context, data) + except Exception as e: + log.warn('aborting the bar on error {}'.format(e)) + context.errors.append(e) + + log.debug('completed bar {}, total execution errors {}'.format( + data.current_dt, + len(context.errors) + )) + + if len(context.errors) > 0: + log.info('the errors:\n{}'.format(context.errors)) + + +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.symbol)) + (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, + 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() + pass + + +# run_algorithm( +# initialize=initialize, +# handle_data=handle_data, +# analyze=analyze, +# exchange_name='bitfinex', +# live=True, +# algo_namespace=algo_namespace, +# base_currency='btc', +# live_graph=False +# ) + +# Backtest +run_algorithm( + capital_base=10000, + data_frequency='minute', + initialize=initialize, + handle_data=handle_data, + analyze=analyze, + exchange_name='bitfinex', + algo_namespace=algo_namespace, + base_currency='usd', + start=pd.to_datetime('2017-9-10', utc=True), + end=pd.to_datetime('2017-10-22', utc=True), +) diff --git a/tests/exchange/__init.py b/tests/exchange/__init.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/exchange/base.py b/tests/exchange/base.py index b98c3ec1..da67e8f0 100644 --- a/tests/exchange/base.py +++ b/tests/exchange/base.py @@ -1,4 +1,3 @@ -import unittest from abc import ABCMeta, abstractmethod diff --git a/tests/exchange/test_bundle.py b/tests/exchange/test_bundle.py index 9c39e4e4..69b31400 100644 --- a/tests/exchange/test_bundle.py +++ b/tests/exchange/test_bundle.py @@ -137,10 +137,10 @@ class TestExchangeBundle: log.info('ingesting exchange bundle {}'.format(exchange_name)) exchange_bundle.ingest( data_frequency=data_frequency, - include_symbols=include_symbols, + include_symbols=None, exclude_symbols=None, - start=start, - end=end, + start=None, + end=None, show_progress=True ) diff --git a/tests/exchange/test_data_portal.py b/tests/exchange/test_data_portal.py index 8a605a0b..31c67295 100644 --- a/tests/exchange/test_data_portal.py +++ b/tests/exchange/test_data_portal.py @@ -1,47 +1,37 @@ import pandas as pd +from catalyst.exchange.exchange_data_portal import DataPortalExchangeBacktest, \ + DataPortalExchangeLive from logbook import Logger +from test_utils import rnd_history_date_days, rnd_bar_count from catalyst import get_calendar from catalyst.exchange.asset_finder_exchange import AssetFinderExchange from catalyst.exchange.bitfinex.bitfinex import Bitfinex from catalyst.exchange.bittrex.bittrex import Bittrex -from catalyst.exchange.data_portal_exchange import DataPortalExchangeBacktest, \ - DataPortalExchangeLive -from catalyst.exchange.exchange_utils import get_exchange_auth +from catalyst.exchange.exchange_utils import get_exchange_auth, \ + get_common_assets +from catalyst.exchange.factory import get_exchange, get_exchanges log = Logger('test_bitfinex') -class TestExchangeDataPortalTestCase: +class TestExchangeDataPortal: @classmethod def setup(self): log.info('creating bitfinex exchange') - auth_bitfinex = get_exchange_auth('bitfinex') - self.bitfinex = Bitfinex( - key=auth_bitfinex['key'], - secret=auth_bitfinex['secret'], - base_currency='usd' - ) - - log.info('creating bittrex exchange') - auth_bitfinex = get_exchange_auth('bittrex') - self.bittrex = Bittrex( - key=auth_bitfinex['key'], - secret=auth_bitfinex['secret'], - base_currency='usd' - ) - + exchanges = get_exchanges(['bitfinex', 'bittrex', 'poloniex']) open_calendar = get_calendar('OPEN') asset_finder = AssetFinderExchange() self.data_portal_live = DataPortalExchangeLive( - exchanges=dict(bitfinex=self.bitfinex, bittrex=self.bittrex), + exchanges=exchanges, asset_finder=asset_finder, trading_calendar=open_calendar, first_trading_day=pd.to_datetime('today', utc=True) ) + self.data_portal_backtest = DataPortalExchangeBacktest( - exchanges=dict(bitfinex=self.bitfinex), + exchanges=exchanges, asset_finder=asset_finder, trading_calendar=open_calendar, first_trading_day=None # will set dynamically based on assets @@ -106,3 +96,20 @@ class TestExchangeDataPortalTestCase: assets, 'close', date, 'minute') log.info('found spot value {}'.format(value)) pass + + def test_history_compare_exchanges(self): + exchanges = get_exchanges(['bittrex', 'bitfinex', 'poloniex']) + assets = get_common_assets(exchanges) + + date = rnd_history_date_days() + bar_count = rnd_bar_count() + data = self.data_portal_backtest.get_history_window( + assets=assets, + end_dt=date, + bar_count=bar_count, + frequency='1d', + field='close', + data_frequency='daily' + ) + + log.info('found history window: {}'.format(data)) diff --git a/tests/exchange/test_utils.py b/tests/exchange/test_utils.py new file mode 100644 index 00000000..eb53bff5 --- /dev/null +++ b/tests/exchange/test_utils.py @@ -0,0 +1,17 @@ +from datetime import timedelta +from random import randint + +import pandas as pd + + +def rnd_history_date_days(max_days=30): + now = pd.Timestamp.utcnow() + days = randint(0, max_days) + + return now - timedelta(days=days) + + +def rnd_bar_count(max_bars=21): + now = pd.Timestamp.utcnow() + + return randint(0, max_bars) From 032c7fd16b28a1b72b97c481b2aac9205668ddf8 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Fri, 27 Oct 2017 00:57:52 -0400 Subject: [PATCH 03/24] Improved frequency support for data.history() in backtest, standardized class names, improved unit tests and working on new sample algo. --- conatus_strategies/neo_rsi.py | 197 ---------------------------------- 1 file changed, 197 deletions(-) delete mode 100644 conatus_strategies/neo_rsi.py diff --git a/conatus_strategies/neo_rsi.py b/conatus_strategies/neo_rsi.py deleted file mode 100644 index f1569d2c..00000000 --- a/conatus_strategies/neo_rsi.py +++ /dev/null @@ -1,197 +0,0 @@ -import talib -import pandas as pd -from logbook import Logger - -from catalyst.api import ( - order, - order_target_percent, - symbol, - record, - get_open_orders, -) -from catalyst.exchange.stats_utils import get_pretty_stats -from catalyst.utils.run_algo import run_algorithm - -algo_namespace = 'neo_rsi' -log = Logger(algo_namespace) - - -def initialize(context): - log.info('initializing algo') - context.asset = symbol('neo_usd', 'bitfinex') - - context.BUY_SIGNAL = 30 - context.SELL_SIGNAL = 70 - context.SLIPPAGE_ALLOWED = 0.02 - - context.errors = [] - pass - - -def _handle_data(context, data): - dt = data.current_dt - log.info('BAR {}'.format(dt)) - - price = data.current(context.asset, 'close') - log.info('got price {price}'.format(price=price)) - - if price is None: - log.warn('no pricing data') - return - - try: - prices = data.history( - context.asset, - fields='price', - bar_count=15, - frequency='15m' - ) - except Exception as e: - log.warn('historical data not available: '.format(e)) - return - - rsi = talib.RSI(prices.values, timeperiod=14)[-1] - log.info('got rsi: {}'.format(rsi)) - - cash = context.portfolio.cash - log.info('base currency available: {cash}'.format(cash=cash)) - - - orders = get_open_orders(context.asset) - # if len(orders) > 0: - # log.info('skipping bar until all open orders execute') - # return - - if context.asset in context.portfolio.positions: - if rsi >= context.SELL_SIGNAL: - position = context.portfolio.positions[context.asset] - log.info('closing position') - amount = -position.amount - expected_proceeds = -(amount * price) - order( - asset=context.asset, - amount=amount, - limit_price=price * (1 - context.SLIPPAGE_ALLOWED), - ) - else: - if rsi <= context.BUY_SIGNAL: - log.info('opening position') - order( - asset=context.asset, - amount=cash / price, - limit_price=price * (1 + context.SLIPPAGE_ALLOWED), - ) - - volume = data.current(context.asset, 'volume') - record( - price=price, - volume=volume, - cash=cash, - starting_cash=context.portfolio.starting_cash, - leverage=context.account.leverage, - rsi=rsi - ) - - -def handle_data(context, data): - try: - _handle_data(context, data) - except Exception as e: - log.warn('aborting the bar on error {}'.format(e)) - context.errors.append(e) - - log.debug('completed bar {}, total execution errors {}'.format( - data.current_dt, - len(context.errors) - )) - - if len(context.errors) > 0: - log.info('the errors:\n{}'.format(context.errors)) - - -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.symbol)) - (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, - 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() - pass - - -# run_algorithm( -# initialize=initialize, -# handle_data=handle_data, -# analyze=analyze, -# exchange_name='bitfinex', -# live=True, -# algo_namespace=algo_namespace, -# base_currency='btc', -# live_graph=False -# ) - -# Backtest -run_algorithm( - capital_base=10000, - data_frequency='minute', - initialize=initialize, - handle_data=handle_data, - analyze=analyze, - exchange_name='bitfinex', - algo_namespace=algo_namespace, - base_currency='usd', - start=pd.to_datetime('2017-9-10', utc=True), - end=pd.to_datetime('2017-10-22', utc=True), -) From 7465e8e432486839361010433d1010f75363004b Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Mon, 30 Oct 2017 11:16:20 -0600 Subject: [PATCH 04/24] DOC: videos --- docs/source/index.rst | 1 + docs/source/videos.rst | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 docs/source/videos.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 0efccaeb..c3e0a19a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -10,6 +10,7 @@ Table of Contents install beginner-tutorial naming-convention + videos .. bundles .. development-guidelines .. appendix diff --git a/docs/source/videos.rst b/docs/source/videos.rst new file mode 100644 index 00000000..31bcbc86 --- /dev/null +++ b/docs/source/videos.rst @@ -0,0 +1,17 @@ +Videos +====== + + +Installation: MacOS +------------------- + +.. raw:: html + + + +| +| +Installation: Windows +--------------------- + +Coming up next! \ No newline at end of file From eaefe4a9085edcf271481270da4d3b6296f2f04f Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Mon, 30 Oct 2017 13:27:52 -0600 Subject: [PATCH 05/24] DOC: videos --- docs/source/videos.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/source/videos.rst b/docs/source/videos.rst index 31bcbc86..8fdfae2a 100644 --- a/docs/source/videos.rst +++ b/docs/source/videos.rst @@ -14,4 +14,13 @@ Installation: MacOS Installation: Windows --------------------- -Coming up next! \ No newline at end of file +Where things go smoothly: + +.. raw:: html + + + +| +Where things don't: + + Coming up next! \ No newline at end of file From 13f023d3644c63ac4829c91052976e0b416d91a2 Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Mon, 30 Oct 2017 13:54:27 -0600 Subject: [PATCH 06/24] DOC: documenting the documentation --- docs/source/development-guidelines.rst | 135 ++++++------------------- docs/source/index.rst | 1 + 2 files changed, 31 insertions(+), 105 deletions(-) diff --git a/docs/source/development-guidelines.rst b/docs/source/development-guidelines.rst index d643090d..9bc6c7b0 100644 --- a/docs/source/development-guidelines.rst +++ b/docs/source/development-guidelines.rst @@ -1,21 +1,17 @@ Development Guidelines ====================== -This page is intended for developers of Zipline, people who want to contribute to the Zipline codebase or documentation, or people who want to install from source and make local changes to their copy of Zipline. +This page is intended for developers of Catalyst, people who want to contribute to the Catalyst codebase or documentation, or people who want to install from source and make local changes to their copy of Catalyst. -All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We `track issues`__ on `GitHub`__ and also have a `mailing list`__ where you can ask questions. - -__ https://github.com/quantopian/zipline/issues -__ https://github.com/ -__ https://groups.google.com/forum/#!forum/zipline +All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We `track issues `_ on `GitHub `_ and also have a `discord group `_ where you can ask questions. Creating a Development Environment ---------------------------------- -First, you'll need to clone Zipline by running: +First, you'll need to clone Catalyst by running: .. code-block:: bash - $ git clone git@github.com:your-github-username/zipline.git + $ git clone git@github.com:enigmampc/catalyst.git Then check out to a new branch where you can make your changes: @@ -23,15 +19,13 @@ Then check out to a new branch where you can make your changes: $ git checkout -b some-short-descriptive-name -If you don't already have them, you'll need some C library dependencies. You can follow the `install guide`__ to get the appropriate dependencies. - -__ install.html +If you don't already have them, you'll need some C library dependencies. You can follow the `install guide `_ to get the appropriate dependencies. The following section assumes you already have virtualenvwrapper and pip installed on your system. Suggested installation of Python library dependencies used for development: .. code-block:: bash - $ mkvirtualenv zipline + $ mkvirtualenv catalyst $ ./etc/ordered_pip.sh ./etc/requirements.txt $ pip install -r ./etc/requirements_dev.txt $ pip install -r ./etc/requirements_blaze.txt @@ -42,104 +36,39 @@ Finally, you can build the C extensions by running: $ python setup.py build_ext --inplace -To finish, make sure `tests`__ pass. +.. To finish, make sure `tests`__ pass. -__ #style-guide-running-tests +.. __ #style-guide-running-tests -If you get an error running nosetests after setting up a fresh virtualenv, please try running +.. If you get an error running nosetests after setting up a fresh virtualenv, please try running -.. code-block:: bash +.. code-block - # where zipline is the name of your virtualenv - $ deactivate zipline - $ workon zipline +.. # where zipline is the name of your virtualenv +.. $ deactivate zipline +.. $ workon zipline -Development with Docker +.. Development with Docker +.. ----------------------- + +..If you want to work with zipline using a `Docker`__ container, you'll need to build the ``Dockerfile`` in the Zipline root directory, and then build ``Dockerfile-dev``. Instructions for building both containers can be found in ``Dockerfile`` and ``Dockerfile-dev``, respectively. + +.. __ https://docs.docker.com/get-started/ + +Git Branching Structure ----------------------- -If you want to work with zipline using a `Docker`__ container, you'll need to build the ``Dockerfile`` in the Zipline root directory, and then build ``Dockerfile-dev``. Instructions for building both containers can be found in ``Dockerfile`` and ``Dockerfile-dev``, respectively. +If you want to contribute to the codebase of Catalyst, familiarize yourself with our branching structure, a fairly standardized one for that matter, that follows what is documented in the following article: `A successful Git branching model `_. To contribute, create your local branch and submit a Pull Request (PR) to the **develop** branch. -__ https://docs.docker.com/get-started/ +.. image:: https://camo.githubusercontent.com/9bde6fb64a9542a572e0e2017cbb58d9d2c440ac/687474703a2f2f6e7669652e636f6d2f696d672f6769742d6d6f64656c4032782e706e67 - -Style Guide & Running Tests ---------------------------- - -We use `flake8`__ for checking style requirements and `nosetests`__ to run Zipline tests. Our `continuous integration`__ tools will run these commands. - -__ http://flake8.pycqa.org/en/latest/ -__ http://nose.readthedocs.io/en/latest/ -__ https://en.wikipedia.org/wiki/Continuous_integration - -Before submitting patches or pull requests, please ensure that your changes pass when running: - -.. code-block:: bash - - $ flake8 zipline tests - -In order to run tests locally, you'll need `TA-lib`__, which you can install on Linux by running: - -__ https://mrjbq7.github.io/ta-lib/install.html - -.. code-block:: bash - - $ wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz - $ tar -xvzf ta-lib-0.4.0-src.tar.gz - $ cd ta-lib/ - $ ./configure --prefix=/usr - $ make - $ sudo make install - -And for ``TA-lib`` on OS X you can just run: - -.. code-block:: bash - - $ brew install ta-lib - -Then run ``pip install`` TA-lib: - -.. code-block:: bash - - $ pip install -r ./etc/requirements_talib.txt - -You should now be free to run tests: - -.. code-block:: bash - - $ nosetests - - -Continuous Integration ----------------------- - -We use `Travis CI`__ for Linux-64 bit builds and `AppVeyor`__ for Windows-64 bit builds. - -.. note:: - - We do not currently have CI for OSX-64 bit builds. 32-bit builds may work but are not included in our integration tests. - -__ https://travis-ci.org/quantopian/zipline -__ https://ci.appveyor.com/project/quantopian/zipline - - -Packaging ---------- -To learn about how we build Zipline conda packages, you can read `this`__ section in our release process notes. - -__ release-process.html#uploading-conda-packages - Contributing to the Docs ------------------------ -If you'd like to contribute to the documentation on zipline.io, you can navigate to ``docs/source/`` where each `reStructuredText`__ (``.rst``) file is a separate section there. To add a section, create a new file called ``some-descriptive-name.rst`` and add ``some-descriptive-name`` to ``appendix.rst``. To edit a section, simply open up one of the existing files, make your changes, and save them. - -__ https://en.wikipedia.org/wiki/ReStructuredText - -We use `Sphinx`__ to generate documentation for Zipline, which you will need to install by running: - -__ http://www.sphinx-doc.org/en/stable/ +If you'd like to contribute to the documentation on enigmampc.github.io, you can navigate to ``docs/source/`` where each `reStructuredText `_ file is a separate section there. To add a section, create a new file called ``some-descriptive-name.rst`` and add ``some-descriptive-name`` to ``index.rst``. To edit a section, simply open up one of the existing files, make your changes, and save them. +We use `Sphinx `_ to generate documentation for Catalyst, which you will need to install by running: .. code-block:: bash @@ -149,7 +78,7 @@ To build and view the docs locally, run: .. code-block:: bash - # assuming you're in the Zipline root directory + # assuming you're in the Catalyst root directory $ cd docs $ make html $ {BROWSER} build/html/index.html @@ -162,7 +91,7 @@ Standard prefixes to start a commit message: .. code-block:: text - BLD: change related to building Zipline + BLD: change related to building Catalyst BUG: bug fix DEP: deprecate something, or remove a deprecated object DEV: development tool or utility @@ -172,15 +101,13 @@ Standard prefixes to start a commit message: REV: revert an earlier commit STY: style fix (whitespace, PEP8, flake8, etc) TST: addition or modification of tests - REL: related to releasing Zipline + REL: related to releasing Catalyst PERF: performance enhancements Some commit style guidelines: -Commit lines should be no longer than `72 characters`__. The first line of the commit should include one of the above prefixes. There should be an empty line between the commit subject and the body of the commit. In general, the message should be in the imperative tense. Best practice is to include not only what the change is, but why the change was made. - -__ https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project +Commit lines should be no longer than `72 characters `_. The first line of the commit should include one of the above prefixes. There should be an empty line between the commit subject and the body of the commit. In general, the message should be in the imperative tense. Best practice is to include not only what the change is, but why the change was made. **Example:** @@ -203,8 +130,6 @@ __ https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project Formatting Docstrings --------------------- -When adding or editing docstrings for classes, functions, etc, we use `numpy`__ as the canonical reference. - -__ https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt +When adding or editing docstrings for classes, functions, etc, we use `numpy `_ as the canonical reference. diff --git a/docs/source/index.rst b/docs/source/index.rst index c3e0a19a..f0efbd6d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -11,6 +11,7 @@ Table of Contents beginner-tutorial naming-convention videos + development-guidelines .. bundles .. development-guidelines .. appendix From 0b28bf0e96e670d492344fcea63e53157ef1f843 Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Mon, 30 Oct 2017 14:22:55 -0600 Subject: [PATCH 07/24] DOC: live trading --- docs/live-trading-wiki.md | 105 ------------------------------- docs/source/index.rst | 1 + docs/source/live-trading.rst | 118 +++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 105 deletions(-) delete mode 100644 docs/live-trading-wiki.md create mode 100644 docs/source/live-trading.rst diff --git a/docs/live-trading-wiki.md b/docs/live-trading-wiki.md deleted file mode 100644 index a7dbd9f1..00000000 --- a/docs/live-trading-wiki.md +++ /dev/null @@ -1,105 +0,0 @@ -

Live Trading

-This document explains how to get started with live trading. - -

Supported Exchanges

-Catalyst can trade against these exchanges: - -* Bitfinex, id=`bitfinex` -* Bittrex, id=`bittrex` - -

Authentication

-Most exchanges require key/token combination for authentication. By -convention, Catalyst uses an "auth.json" file to hold this data. - -This example illustrates the convention using the Bitfinex exchange. -Here is how to generate key and secret values for bitfinex: -https://docs.bitfinex.com/v1/docs/api-access. Most exchanges follow -a similar process. - -The auth.json file: -```json -{ - "name": "bitfinex", - "key": "my-key", - "secret": "my-secret" -} -``` - -The file goes here: -``` -~/.catalyst/data/exchanges/bitfinex/auth.json -``` - -Note that the 'bitfinex' directory corresponds to the id of the Bitfinex -exchange as defined in the "Supported Exchanges" section above. -Attempting to run an algorithm where the targeted exchange is missing -its "auth.json" file will create the directory structure but result -in an error. - -

Currency Symbols

-Catalyst introduces a universal convention to reference -trading pairs and individual currencies. This -is required to ensure that the `symbol()` api predictably -returns the correct asset regardless of the targeted exchange. - -Exchanges tend to use their own convention to represent currencies -(e.g. XBT and BTC both represent Bitcoin on different exchanges). -Trading pairs are also inconsistent. For example, Bitfinex -puts the market currency before the base currency without a -separator, Bittrex puts the base currency first and uses a dash -seperator. - -Here is the Catalyst convention: - -*[Market Currency]_[Base Currency]* all lowercase. - -Currency symbols (e.g. btc, eth, ltc) follow the Bittrex convention. - -Here are some examples: -```python -# With Bitfinex -bitcoin_usd_asset = symbol('btc_usd') -ethereum_bitcoin_asset = symbol('eth_btc') - -# With Bittrex -ethereum_bitcoin_asset = symbol('eth_btc') -neo_ethereum_asset = symbol('neo_eth) -``` - -Note that the trading pairs are always referenced in the same manner. -However, not all trading pairs are available on all exchanges. An -error will occur if the specified trading pair is not trading -on the exchange. - -

Trading an Algorithm

-There is no special convention to follow when writing an -algorithm for live trading. The same algorithm should work in -backtest and live execution mode without modification. - -What differs are the arguments provided to the catalyst client or -`run_algorithm()` interface. Here is example: - -```python -run_algorithm( - initialize=initialize, - handle_data=handle_data, - analyze=analyze, - exchange_name='bitfinex', - live=True, - algo_namespace='my_algo_trading_xrp', - base_currency='btc' -) -``` - -Here is the breakdown of the new arguments: -* live: Boolean flag which enables live trading. -* exchange_name: The name of the targeted exchange - (supported values: *bitfinex*, *bittrex*). -* algo_namespace: A arbitrary label assigned to your algorithm for - data storage purposes. -* 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. - -Here is a complete algorithm for reference: -[Buy Low and Sell High](../catalyst/examples/buy_low_sell_high_live.py) diff --git a/docs/source/index.rst b/docs/source/index.rst index f0efbd6d..a4554bda 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -9,6 +9,7 @@ Table of Contents install beginner-tutorial + live-trading naming-convention videos development-guidelines diff --git a/docs/source/live-trading.rst b/docs/source/live-trading.rst new file mode 100644 index 00000000..7a471d68 --- /dev/null +++ b/docs/source/live-trading.rst @@ -0,0 +1,118 @@ +Live Trading +============ +This document explains how to get started with live trading. + +Supported Exchanges +^^^^^^^^^^^^^^^^^^^ +Catalyst can trade against these exchanges: + +- Bitfinex, id= ``bitfinex`` +- Bittrex, id= ``bittrex`` +- Poloniex, id= ``poloniex`` + +Authentication +^^^^^^^^^^^^^^ +Most exchanges require token key/secret combination for authentication. By +convention, Catalyst uses an ``auth.json`` file to hold this data. + +This example illustrates the convention using the *Bitfinex* exchange. +Here is how to generate key and secret values for the Bitfinex exchange: +https://docs.bitfinex.com/v1/docs/api-access. Most exchanges follow +a similar process. + +The auth.json file: + +.. code-block:: json + + { + "name": "bitfinex", + "key": "my-key", + "secret": "my-secret" + } + + +The file goes here: ``~/.catalyst/data/exchanges/bitfinex/auth.json`` + +Note that the `bitfinex` part in the directory above corresponds to the id of the Bitfinex +exchange as defined in the "Supported Exchanges" section above. +Attempting to run an algorithm where the targeted exchange is missing +its ``auth.json`` file will create the directory structure and create an empty +auth.json file, but will result in an error. + +Currency Symbols +^^^^^^^^^^^^^^^^ +Catalyst introduces a universal convention to reference +trading pairs and individual currencies. This +is required to ensure that the ``symbol()`` api predictably +returns the correct asset regardless of the targeted exchange. + +Exchanges tend to use their own convention to represent currencies +(e.g. XBT and BTC both represent Bitcoin on different exchanges). +Trading pairs are also inconsistent. For example, Bitfinex +puts the market currency before the base currency without a +separator, Bittrex puts the base currency first and uses a dash +seperator. + +Here is the Catalyst convention: + +*[Market Currency]_[Base Currency]* all lowercase. + +Currency symbols (e.g. btc, eth, ltc) follow the Bittrex convention. + +Here are some examples: + +.. code-block:: json + + # With Bitfinex + bitcoin_usd_asset = symbol('btc_usd') + ethereum_bitcoin_asset = symbol('eth_btc') + + # With Bittrex + ethereum_bitcoin_asset = symbol('eth_btc') + neo_ethereum_asset = symbol('neo_eth) + +Note that the trading pairs are always referenced in the same manner. +However, not all trading pairs are available on all exchanges. An +error will occur if the specified trading pair is not trading +on the exchange. To check which currency pairs are available on each +of the supported exchanges, see `Catalyst Market Coverage `_ From 8132e1f5eac1cfdc1986f3a985ae43b957e52184 Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Mon, 30 Oct 2017 14:47:15 -0600 Subject: [PATCH 08/24] DOC: small fixes --- docs/source/beginner-tutorial.rst | 2 +- docs/source/install.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/beginner-tutorial.rst b/docs/source/beginner-tutorial.rst index a19374ee..a54e4922 100644 --- a/docs/source/beginner-tutorial.rst +++ b/docs/source/beginner-tutorial.rst @@ -255,7 +255,7 @@ slippage model that ``catalyst`` uses). Let's take a quick look at the performance ``DataFrame``. For this, we use ``pandas`` from inside the IPython Notebook and print the first ten -rows. and print the first ten rows. Note that ``catalyst`` makes heavy usage of +rows. Note that ``catalyst`` makes heavy usage of `pandas `_, especially for data input and outputting so it's worth spending some time to learn it. diff --git a/docs/source/install.rst b/docs/source/install.rst index 028f9295..d47590e9 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -41,7 +41,7 @@ version: $ virtualenv catalyst-venv $ source ./catalyst-venv/bin/activate - $ pip install enigma- + $ pip install enigma-catalyst Though not required by Catalyst directly, our example algorithms use matplotlib to visually display the results of the trading algorithms. If you wish to run From 6a0d0a0422aa3973bff6a205a688742fb2f2291c Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Mon, 30 Oct 2017 15:14:27 -0600 Subject: [PATCH 09/24] DOC: jupyter notebook fix --- docs/source/beginner-tutorial.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/beginner-tutorial.rst b/docs/source/beginner-tutorial.rst index c4b516b2..6848a806 100644 --- a/docs/source/beginner-tutorial.rst +++ b/docs/source/beginner-tutorial.rst @@ -486,6 +486,10 @@ collect, the second argument is the unit (either ``'1d'`` for ``'1m'`` but note that you need to have minute-level data for using ``1m``). This is a function we use in the ``handle_data()`` section: +.. code-block:: python + + %load_ext catalyst + .. code-block:: python %%catalyst --start 2016-4-1 --end 2017-9-30 -x bitfinex From 06c8ab9c376f45e2ac73661f34f2c4f547489f3b Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Mon, 30 Oct 2017 15:57:19 -0600 Subject: [PATCH 10/24] DOC: troubleshooting Windows install --- docs/source/install.rst | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/docs/source/install.rst b/docs/source/install.rst index 3db00966..34855618 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -133,13 +133,34 @@ it and install it before proceeding to the next step. For windows, the easiest and best supported way to install Catalyst is to use :ref:`Conda `. -Troubleshooting Visual C++ Compiler Install -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Some problems we have encountered installing the **Visual C++ Compiler** mentioned above +are as follows: -We run into two different errors when trying to install the the `Microsoft Visual C++ -Compiler for Python 2.7` mentioned above: +- **The system administrator has set policies to prevent this installation**. + + In some systems, there is a default *Windows Software Restriction* policy that + prevents the installation of some software packages like this one. You'll have + to change the Registry to circumvent this: -- + - Click ``Start``, and search for ``regedit`` and launch the ``Registry Editor`` + - Navigate to the following folder: + ``HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Installer`` + - If there is an entry for ``DisableMSI``, set the Value data to 0. + - If there is no such entry, click on the ``Edit`` menu -> ``New`` -> ``DWORD (32-bit) Value`` + and enter ``DisableMSI`` as the Name (and by default you get 0 as the Value Data) + +| +- **The installer has encountered an unexpected error installing this package. + This may indicate a problem with this package. The error code is 2503.** + + We have observed this when trying to install a package without enough administrator + permissions. Even when you are logged in as an Administrator, you have to explictily + install this package with administrator privileges: + + - Click ``Start`` and find ``CMD`` or ``Command Prompt`` + - Right click on it and choose ``Run as administrator`` + - ``cd`` into the folder where you downloaded ``VCForPython27.msi`` + - Run ``msiexec /i VCForPython27.msi`` Amazon Linux AMI From 3394614ecfc537e12100f01930f6b113928c9cf7 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Mon, 30 Oct 2017 21:17:53 -0400 Subject: [PATCH 11/24] BUG: Fixes issue #47. Made improvements around auto-ingestion. --- catalyst/exchange/bundle_utils.py | 49 +++- catalyst/exchange/exchange_bundle.py | 264 ++++++++++++---------- catalyst/exchange/exchange_data_portal.py | 26 ++- catalyst/exchange/stats_utils.py | 40 ++++ catalyst/support/issue_47.py | 140 ++++++++++++ tests/exchange/test_bundle.py | 65 +++++- 6 files changed, 446 insertions(+), 138 deletions(-) create mode 100644 catalyst/support/issue_47.py diff --git a/catalyst/exchange/bundle_utils.py b/catalyst/exchange/bundle_utils.py index acd2cc4b..9a0eb3c2 100644 --- a/catalyst/exchange/bundle_utils.py +++ b/catalyst/exchange/bundle_utils.py @@ -103,34 +103,59 @@ def get_start_dt(end_dt, bar_count, data_frequency): return start_dt -def get_month_start_end(dt): +def get_period_label(dt, data_frequency): """ - Returns the first and last day of the month for the specified date. + The period label for the specified date and frequency. :param dt: + :param data_frequency: + :return: + """ + return '{}-{:02d}'.format(dt.year, dt.month) if data_frequency == 'minute' \ + else '{}'.format(dt.year) + + +def get_month_start_end(dt, first_day=None, last_day=None): + """ + The first and last day of the month for the specified date. + + :param dt: + :param first_day + :param last_day :return: """ month_range = calendar.monthrange(dt.year, dt.month) - month_start = pd.to_datetime(datetime( - dt.year, dt.month, 1, 0, 0, 0, 0 - ), utc=True) - month_end = pd.to_datetime(datetime( - dt.year, dt.month, month_range[1], 23, 59, 0, 0 - ), utc=True) + if first_day: + month_start = first_day + else: + month_start = pd.to_datetime(datetime( + dt.year, dt.month, 1, 0, 0, 0, 0 + ), utc=True) + + if last_day: + month_end = last_day + else: + month_end = pd.to_datetime(datetime( + dt.year, dt.month, month_range[1], 23, 59, 0, 0 + ), utc=True) return month_start, month_end -def get_year_start_end(dt): +def get_year_start_end(dt, first_day=None, last_day=None): """ - Returns the first and last day of the year for the specified date. + The first and last day of the year for the specified date. :param dt: + :param first_day + :param last_day :return: """ - year_start = pd.to_datetime(date(dt.year, 1, 1), utc=True) - year_end = pd.to_datetime(date(dt.year, 12, 31), utc=True) + year_start = first_day if first_day \ + else pd.to_datetime(date(dt.year, 1, 1), utc=True) + year_end = last_day if last_day \ + else pd.to_datetime(date(dt.year, 12, 31), utc=True) return year_start, year_end diff --git a/catalyst/exchange/exchange_bundle.py b/catalyst/exchange/exchange_bundle.py index 353c5fc9..ec499334 100644 --- a/catalyst/exchange/exchange_bundle.py +++ b/catalyst/exchange/exchange_bundle.py @@ -1,9 +1,14 @@ import os +import os import shutil -from datetime import timedelta +from itertools import chain import pandas as pd +from catalyst.assets._assets import TradingPair from logbook import Logger +from pandas.tslib import Timestamp +from pytz import UTC +from six import itervalues from catalyst import get_calendar from catalyst.constants import LOG_LEVEL @@ -11,11 +16,11 @@ from catalyst.data.minute_bars import BcolzMinuteOverlappingData, \ BcolzMinuteBarMetadata from catalyst.exchange.bundle_utils import range_in_bundle, \ get_bcolz_chunk, get_delta, get_month_start_end, \ - get_year_start_end, get_df_from_arrays, get_start_dt + get_year_start_end, get_df_from_arrays, get_start_dt, get_period_label from catalyst.exchange.exchange_bcolz import BcolzExchangeBarReader, \ BcolzExchangeBarWriter from catalyst.exchange.exchange_errors import EmptyValuesInBundleError, \ - InvalidHistoryFrequencyError, TempBundleNotFoundError, \ + TempBundleNotFoundError, \ NoDataAvailableOnExchange, \ PricingDataNotLoadedError from catalyst.exchange.exchange_utils import get_exchange_folder @@ -383,7 +388,7 @@ class ExchangeBundle: """ reader = self.get_reader(data_frequency) - chunks = [] + chunks = dict() for asset in assets: try: # Checking if the the asset has price data in the specified @@ -397,98 +402,57 @@ class ExchangeBundle: log.debug('skipping {}: {}'.format(asset.symbol, e)) continue - # This is either the first trading day of the asset or the - # first session available in the calendar - first_trading_dt = asset.start_date \ - if asset.start_date > self.calendar.first_session \ - else self.calendar.first_session + dates = pd.date_range( + start=get_period_label(adj_start, data_frequency), + end=get_period_label(adj_end, data_frequency), + freq='MS' if data_frequency == 'minute' else 'AS', + tz=UTC + ) - # Aligning start / end dates with the daily calendar - sessions = self.calendar.sessions_in_range(adj_start, adj_end) + # Adjusting the last date of the range to avoid + # going over the asset's trading bounds + dates.values[0] = adj_start + dates.values[-1] = adj_end - # We loop through each session to create chunks for each period - chunk_labels = [] - dt = sessions[0] - while dt <= sessions[-1]: - label = '{}-{:02d}'.format(dt.year, dt.month) \ - if data_frequency == 'minute' else '{}'.format(dt.year) + chunks[asset] = [] + for index, dt in enumerate(dates): + get_start_end = get_month_start_end \ + if data_frequency == 'minute' else get_year_start_end - if label not in chunk_labels: - chunk_labels.append(label) + period_start, period_end = get_start_end( + dt=dt, + first_day=dt if index == 0 else None, + last_day=dt if index == len(dates) - 1 else None + ) - # Adjusting the period dates to match the availability - # of the trading pair - if data_frequency == 'minute': - period_start, period_end = get_month_start_end(dt) + # Currencies don't always start trading at midnight. + # Checking the last minute of the day instead. + range_start = period_start.replace(hour=23, minute=59) \ + if data_frequency == 'minute' else period_start - asset_start_month, _ = get_month_start_end( - first_trading_dt + # Checking if the data already exists in the bundle + # for the date range of the chunk. If not, we create + # a chunk for ingestion. + has_data = range_in_bundle( + asset, range_start, period_end, reader + ) + if not has_data: + chunks[asset].append( + dict( + asset=asset, + period_start=period_start, + period_end=period_end, + period=get_period_label(dt, data_frequency) ) - if asset_start_month == period_start \ - and period_start < first_trading_dt: - period_start = first_trading_dt - - # TODO: need to filter closed pairs? - _, asset_end_month = get_month_start_end( - asset.end_minute - ) - if asset_end_month == period_end \ - and period_end > asset.end_minute: - period_end = asset.end_minute - - elif data_frequency == 'daily': - period_start, period_end = get_year_start_end(dt) - - asset_start_year, _ = get_year_start_end( - first_trading_dt - ) - if asset_start_year == period_start \ - and period_start < first_trading_dt: - period_start = first_trading_dt - - _, asset_end_year = get_year_start_end( - asset.end_daily - ) - if asset_end_year == period_end \ - and period_end > asset.end_daily: - period_end = asset.end_daily - - else: - raise InvalidHistoryFrequencyError( - frequency=data_frequency - ) - - # Currencies don't always start trading at midnight. - # Checking the last minute of the day instead. - range_start = period_start.replace(hour=23, minute=59) \ - if data_frequency == 'minute' else period_start - - # Checking if the data already exists in the bundle - # for the date range of the chunk. If not, we create - # a chunk for ingestion. - has_data = range_in_bundle( - asset, range_start, period_end, reader ) - if not has_data: - log.debug('adding period: {}'.format(label)) - chunks.append( - dict( - asset=asset, - period_start=period_start, - period_end=period_end, - period=label - ) - ) - dt += timedelta(days=1) - - # We sort the chunks by end date to ingest most recent data first - chunks.sort(key=lambda chunk: chunk['period_end']) + # We sort the chunks by end date to ingest most recent data first + chunks[asset].sort(key=lambda chunk: chunk['period_end']) return chunks - def ingest_assets(self, assets, start_dt, end_dt, data_frequency, - show_progress=False): + def ingest_assets(self, assets, data_frequency, start_dt=None, end_dt=None, + show_progress=False, asset_chunks=False): """ Determine if data is missing from the bundle and attempt to ingest it. @@ -497,6 +461,16 @@ class ExchangeBundle: :param end_dt: :return: """ + + if start_dt is None: + start_dt = self.calendar.first_session + + if end_dt is None: + end_dt = pd.Timestamp.utcnow() + + start_dt, end_dt = self.get_adj_dates( + start_dt, end_dt, assets, data_frequency + ) chunks = self.prepare_chunks( assets=assets, data_frequency=data_frequency, @@ -507,7 +481,8 @@ class ExchangeBundle: # Since chunks are either monthly or yearly, it is possible that # our ingestion data range is greater than specified. We adjust # the boundaries to ensure that the writer can write all data. - for chunk in chunks: + all_chunks = list(chain.from_iterable(itervalues(chunks))) + for chunk in all_chunks: if chunk['period_start'] < start_dt: start_dt = chunk['period_start'] @@ -515,24 +490,49 @@ class ExchangeBundle: end_dt = chunk['period_end'] writer = self.get_writer(start_dt, end_dt, data_frequency) - with maybe_show_progress( - chunks, - show_progress, - label='Fetching {exchange} {frequency} candles: '.format( - exchange=self.exchange.name, - frequency=data_frequency - )) as it: - for chunk in it: - self.ingest_ctable( - asset=chunk['asset'], - data_frequency=data_frequency, - period=chunk['period'], - start_dt=chunk['period_start'], - end_dt=chunk['period_end'], - writer=writer, - empty_rows_behavior='strip', - cleanup=True - ) + + if asset_chunks: + for asset in chunks: + with maybe_show_progress( + chunks[asset], + show_progress, + label='Ingesting {frequency} price data for ' + '{symbol} on {exchange}'.format( + exchange=self.exchange.name, + frequency=data_frequency, + symbol=asset.symbol + )) as it: + for chunk in it: + self.ingest_ctable( + asset=chunk['asset'], + data_frequency=data_frequency, + period=chunk['period'], + start_dt=chunk['period_start'], + end_dt=chunk['period_end'], + writer=writer, + empty_rows_behavior='strip', + cleanup=True + ) + else: + with maybe_show_progress( + all_chunks, + show_progress, + label='Ingesting {frequency} price data on ' + '{exchange}'.format( + exchange=self.exchange.name, + frequency=data_frequency, + )) as it: + for chunk in it: + self.ingest_ctable( + asset=chunk['asset'], + data_frequency=data_frequency, + period=chunk['period'], + start_dt=chunk['period_start'], + end_dt=chunk['period_end'], + writer=writer, + empty_rows_behavior='strip', + cleanup=True + ) def ingest(self, data_frequency, include_symbols=None, exclude_symbols=None, start=None, end=None, @@ -549,20 +549,30 @@ class ExchangeBundle: :return: """ assets = self.get_assets(include_symbols, exclude_symbols) - start_dt, end_dt = self.get_adj_dates( - start, end, assets, data_frequency - ) for frequency in data_frequency.split(','): - self.ingest_assets(assets, start_dt, end_dt, frequency, + self.ingest_assets(assets, frequency, start, end, show_progress) def get_history_window_series_and_load(self, - assets, - end_dt, - bar_count, - field, - data_frequency): + assets, # type: List[TradingPair] + end_dt, # type: Timestamp + bar_count, # type: int + field, # type: str + data_frequency, # type: str + algo_end_dt=None # type: Timestamp + ): + # type: (...) -> Dict[str, Series] + """ + Retrieve price data history, ingest missing data. + + :param assets: + :param end_dt: + :param bar_count: + :param field: + :param data_frequency: + :return: + """ try: series = self.get_history_window_series( assets=assets, @@ -586,9 +596,10 @@ class ExchangeBundle: self.ingest_assets( assets=assets, start_dt=start_dt, - end_dt=end_dt, + end_dt=algo_end_dt, data_frequency=data_frequency, - show_progress=True + show_progress=True, + asset_chunks=True ) series = self.get_history_window_series( assets=assets, @@ -596,12 +607,29 @@ class ExchangeBundle: bar_count=bar_count, field=field, data_frequency=data_frequency, - reset_reader=True + reset_reader=False ) return series - def get_spot_values(self, assets, field, dt, data_frequency, - reset_reader=False): + def get_spot_values(self, + assets, # type: List[TradingPair] + field, # type: str + dt, # type: Timestamp + data_frequency, # type: str + reset_reader=False # type: bool + ): + # type: (...) -> List[float] + """ + The spot values for the gives assets, field and date. Reads from + the exchange data bundle. + + :param assets: + :param field: + :param dt: + :param data_frequency: + :param reset_reader: + :return: + """ values = [] try: reader = self.get_reader(data_frequency) diff --git a/catalyst/exchange/exchange_data_portal.py b/catalyst/exchange/exchange_data_portal.py index 3371c584..8d2cf997 100644 --- a/catalyst/exchange/exchange_data_portal.py +++ b/catalyst/exchange/exchange_data_portal.py @@ -301,7 +301,7 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase): :param ffill: :return: """ - bundle = self.exchange_bundles[exchange.name] + bundle = self.exchange_bundles[exchange.name] # type: ExchangeBundle candle_size, unit, data_frequency = get_frequency( frequency, data_frequency @@ -313,14 +313,32 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase): end_dt=end_dt, bar_count=adj_bar_count, field=field, - data_frequency=data_frequency + data_frequency=data_frequency, + algo_end_dt=self._last_available_session, ) df = resample_history_df(pd.DataFrame(series), candle_size, field) return df - def get_exchange_spot_value(self, exchange, assets, field, dt, - data_frequency): + def get_exchange_spot_value(self, + exchange, # type: Exchange + assets, # type: List[TradingPair] + field, # type: str + dt, # type: Timestamp + data_frequency # type: str + ): + # type: (...) -> float + """ + A spot value for the exchange bundle. Try to ingest data if not in + the bundle. + + :param exchange: + :param assets: + :param field: + :param dt: + :param data_frequency: + :return: + """ bundle = self.exchange_bundles[exchange.name] if data_frequency == 'daily': diff --git a/catalyst/exchange/stats_utils.py b/catalyst/exchange/stats_utils.py index 602694d6..bd968dfe 100644 --- a/catalyst/exchange/stats_utils.py +++ b/catalyst/exchange/stats_utils.py @@ -1,4 +1,44 @@ import pandas as pd +import numpy as np + + +def crossover(source, target): + """ + The `x`-series is defined as having crossed over `y`-series if the value + of `x` is greater than the value of `y` and the value of `x` was less than + the value of `y` on the bar immediately preceding the current bar. + + :param source: + :param target: + :return: + """ + if source[-1] is np.nan or source[-2] is np.nan \ + or target[-1] is np.nan or target[-2] is np.nan: + return False + + if source[-1] > target[-1] and source[-2] < target[-2]: + return True + else: + return False + + +def crossunder(source, target): + """ + The `x`-series is defined as having crossed under `y`-series if the value + of `x` is less than the value of `y` and the value of `x` was greater than + the value of `y` on the bar immediately preceding the current bar. + :param source: + :param target: + :return: + """ + if source[-1] is np.nan or source[-2] is np.nan \ + or target[-1] is np.nan or target[-2] is np.nan: + return False + + if source[-1] < target[-1] and source[-2] > target[-2]: + return True + else: + return False def get_pretty_stats(stats_df, recorded_cols=None, num_rows=10): diff --git a/catalyst/support/issue_47.py b/catalyst/support/issue_47.py new file mode 100644 index 00000000..5341dd03 --- /dev/null +++ b/catalyst/support/issue_47.py @@ -0,0 +1,140 @@ +""" +Requires Catalyst version 0.3.0 or above +Tested on Catalyst version 0.3.2 + +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 = 'eth' # 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. + open = fill(data.history(coin, 'open', bar_count=lookback, + frequency='1m')).resample('30T').first() + high = fill(data.history(coin, 'high', bar_count=lookback, + frequency='1m')).resample('30T').max() + low = fill(data.history(coin, 'low', bar_count=lookback, + frequency='1m')).resample('30T').min() + close = fill(data.history(coin, 'price', bar_count=lookback, + frequency='1m')).resample('30T').last() + volume = fill(data.history(coin, 'volume', bar_count=lookback, + frequency='1m')).resample('30T').sum() + + # close[-1] is the equivalent to current price + # displays the minute price for each pair every 30 minutes + print( + today, pair, open[-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 + print(universe_df.head(), len(universe_df)) + 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-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='eth', + live=False, + live_graph=False, + algo_namespace='simple_universe') + +""" +Run in Terminal (inside catalyst environment): +python simple_universe.py +""" diff --git a/tests/exchange/test_bundle.py b/tests/exchange/test_bundle.py index 69b31400..060592ec 100644 --- a/tests/exchange/test_bundle.py +++ b/tests/exchange/test_bundle.py @@ -1,17 +1,20 @@ import hashlib +import tempfile from logging import getLogger +import os import pandas as pd from catalyst import get_calendar from catalyst.exchange.bundle_utils import get_bcolz_chunk, \ - get_periods_range, get_start_dt + get_periods_range, get_start_dt, get_month_start_end, get_df_from_arrays, \ + get_year_start_end from catalyst.exchange.exchange_bcolz import BcolzExchangeBarReader, \ BcolzExchangeBarWriter from catalyst.exchange.exchange_bundle import ExchangeBundle, \ BUNDLE_NAME_TEMPLATE from catalyst.exchange.exchange_utils import get_exchange_folder -from catalyst.exchange.init_utils import get_exchange +from catalyst.exchange.factory import get_exchange from catalyst.exchange.stats_utils import df_to_string from catalyst.utils.paths import ensure_directory @@ -45,11 +48,11 @@ class TestExchangeBundle: exchange = get_exchange(exchange_name) exchange_bundle = ExchangeBundle(exchange) assets = [ - exchange.get_asset('iot_btc') + exchange.get_asset('xmr_btc') ] # start = pd.to_datetime('2017-09-01', utc=True) - start = pd.to_datetime('2017-9-01', utc=True) + start = pd.to_datetime('2016-01-01', utc=True) end = pd.to_datetime('2017-9-30', utc=True) log.info('ingesting exchange bundle {}'.format(exchange_name)) @@ -426,3 +429,57 @@ class TestExchangeBundle: df = pd.DataFrame(bundle_series) print('\n' + df_to_string(df)) pass + + def bundle_to_csv(self): + exchange_name = 'poloniex' + data_frequency = 'daily' + period = '2016' + + exchange = get_exchange(exchange_name) + bundle = ExchangeBundle(exchange) + asset = exchange.get_asset('xmr_btc') + + path = get_bcolz_chunk( + exchange_name=exchange.name, + symbol=asset.symbol, + data_frequency=data_frequency, + period=period + ) + + dt = pd.to_datetime(period, utc=True) + if data_frequency == 'minute': + start_dt, end_dt = get_month_start_end(dt) + else: + start_dt, end_dt = get_year_start_end(dt) + + reader = bundle.get_reader(data_frequency, path=path) + arrays = None + try: + arrays = reader.load_raw_arrays( + sids=[asset.sid], + fields=['open', 'high', 'low', 'close', 'volume'], + start_dt=start_dt, + end_dt=end_dt + ) + except Exception as e: + log.warn('skipping ctable for {} from {} to {}: {}'.format( + asset.symbol, start_dt, end_dt, e + )) + + periods = bundle.get_calendar_periods_range( + start_dt, end_dt, data_frequency + ) + df = get_df_from_arrays(arrays, periods) + + folder = os.path.join( + tempfile.gettempdir(), 'catalyst', exchange.name, asset.symbol + ) + ensure_directory(folder) + + path = os.path.join(folder, period + '.csv') + + log.info('creating csv file: {}'.format(path)) + print('HEAD\n{}'.format(df.head(10))) + print('TAIL\n{}'.format(df.tail(10))) + df.to_csv(path) + pass From 635fc80ef2944647d66654ea2b0512c1273be395 Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Tue, 31 Oct 2017 00:05:48 -0600 Subject: [PATCH 12/24] DOC: fix Windows conda install --- docs/source/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/install.rst b/docs/source/install.rst index 34855618..687092dc 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -327,7 +327,7 @@ for any reason, you can try setting up the environment manually with the followi .. code-block:: bash - conda create --name catalyst python=2.7 scipy + conda create --name catalyst python=2.7 scipy zlib 2. Activate the environment: From 5417a0cdcf6ff1e3d86e2bc748c586ba34b8aa60 Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Tue, 31 Oct 2017 12:12:28 -0600 Subject: [PATCH 13/24] DOC: Release Notes --- docs/source/index.rst | 3 +- docs/source/releases.rst | 137 +++++++++++++++++++++++++++++++++++---- 2 files changed, 128 insertions(+), 12 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index a4554bda..4691f7eb 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -13,8 +13,9 @@ Table of Contents naming-convention videos development-guidelines + releases .. bundles .. development-guidelines .. appendix .. release-process -.. releases + diff --git a/docs/source/releases.rst b/docs/source/releases.rst index 0fce7634..6be74731 100644 --- a/docs/source/releases.rst +++ b/docs/source/releases.rst @@ -2,24 +2,139 @@ Release Notes ============= -.. include:: whatsnew/1.1.1.txt +Version 0.3.3 +^^^^^^^^^^^^^ +**Release Date**: 2017-10-26 -.. include:: whatsnew/1.1.0.txt +Bug Fixes +~~~~~~~~~ -.. include:: whatsnew/1.0.2.txt +- Fix missing -x in ingest-exchange +- Fix issue with daily chunks end date (data bundles) +- Fix issue in the prepare_chunk logic (data bundles) -.. include:: whatsnew/1.0.1.txt +Build +~~~~~ -.. include:: whatsnew/1.0.0.txt +- Added data validation unit tests -.. include:: whatsnew/0.9.0.txt -.. include:: whatsnew/0.8.4.txt +Version 0.3.2 +^^^^^^^^^^^^^ +**Release Date**: 2017-10-25 -.. include:: whatsnew/0.8.3.txt +Bug Fixes +~~~~~~~~~ -.. include:: whatsnew/0.8.0.txt +- Fix to work with empty data bundles +- Fix Windows path of ``$HOME/.catalyst`` folder +- Fix ``etc/python2.7-environment.yml`` for Windows Conda install +- Fix hash method to create sid numbers compatible across platforms +- Fix an issue with asset date in chunks -.. include:: whatsnew/0.7.0.txt +Build +~~~~~ + +- Python3 adjustments +- Added method to clean bundle folders, and remove symbols.json +- Implemented and improved unit tests + + +Version 0.3.1 +^^^^^^^^^^^^^ +**Release Date**: 2017-10-22 + +Bug Fixes +~~~~~~~~~ + +- Fixed OS-dependent path issue in data bundle +- Changed handling of empty ``auth.json``, instead of throwing an error for missing file +- Updated ``etc/python2.7-environment.yml`` to work with Catalyst version 0.3 +- Updated ``catalyst/examples/buy_and_hodl.py`` and ``catalyst/examples/buy_low_sell_high.py`` to work with Catalyst version 0.3 + + +Version 0.3 +^^^^^^^^^^^ +**Release Date**: 2017-10-20 + + + +Version 0.2.dev5 +^^^^^^^^^^^^^^^^ +**Release Date**: 2017-10-03 + +- Fixes bug in data.history function that was formatting 'volume' data as integers, now they are returned as floats with up to 9 decimals of precision. Data bundles redone. + +Version 0.2.dev4 +^^^^^^^^^^^^^^^^ + +**Release Date**: 2017-09-20 + +- Fixes bug in the pricing resolution of 1-minute data, now set to 8 decimal places. Pricing resolution of daily data remains set to 9 decimal places. +- The current data bundle takes 340MB compressed for download, and 460MB uncompressed on disk for Catalyst to use. + +Version 0.2.dev3 +^^^^^^^^^^^^^^^^ + +**Release Date**: 2017-09-20 + +- 1-minute resolution OHLCV data bundle for backtesting from Poloniex exchange +- Implementation of trading of fractional crypto assets (i.e. 0.01 BTC) +- Minimum trade size of a coin can be configured on a per-coin basis, defaults to 0.00000001 in backtesting (most exchanges set the minimum trade to larger amounts, which will impact live trading) +- Increased pricing resolution from 3 to 9 decimal places +- The current data bundle takes 40MB compressed for download, and 99MB uncompressed on disk for Catalyst to use. + +Version 0.2.dev2 +^^^^^^^^^^^^^^^^ + +**Release Date**: 2017-09-07 + +- Fix path issue + +Version 0.2.dev1 +^^^^^^^^^^^^^^^^ + +**Release Date**: 2017-09-03 + +- Implementation of live trading: + + - Comprehensive trading functionality against exchanges Bitfinex and Bittrex. + - Support for all trading pairs available on each exchange. + - Multiple algorithms can trade simultaneously against a single exchange using the same account. + - Each algorithm has a persisted state (i.e. algorithm can be stopped and restarted preserving the state without data loss) that tracks all open orders, executed transactions and portfolio positions. + +- Minute by minute portfolio performance metrics. + + - Daily summary performance statistics compatible with pyfolio, a Python library for performance and risk analysis of financial portfolios + +Version 0.1.dev9 +^^^^^^^^^^^^^^^^ + +**Release Date**: 2017-08-28 + +- Retrieval of crypto benchmark from bundle, instead of hitting Poloniex exchange directly +- Change of bundle storage provider from Dropbox to AWS +- Fix issue with 1/1000 scaling issue of prices in bundle + +Version 0.1.dev8 +^^^^^^^^^^^^^^^^ + +**Release Date**: 2017-08-18 + +- Fixes issue in the creation of bundles (:issue:`27`) + + +Version 0.1.dev7 +^^^^^^^^^^^^^^^^ +- Fixes issues in empty benchmark (:issue:`16`) +- Fixes issue of normalizing timestamps before comparison (:issue:`24`) +- Generic data bundles +- CLI UI improvements + +Version 0.1.dev6 +^^^^^^^^^^^^^^^^ + +**Release Date**: 2017-07-13 + +- Initial public release -.. include:: whatsnew/0.6.1.txt From b39311de850a82b3699ba12a8a6fa3238614dba9 Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Tue, 31 Oct 2017 12:39:35 -0600 Subject: [PATCH 14/24] DOC: Resources page --- docs/source/index.rst | 1 + docs/source/resources.rst | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 docs/source/resources.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 4691f7eb..6ce2f83e 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -12,6 +12,7 @@ Table of Contents live-trading naming-convention videos + resources development-guidelines releases .. bundles diff --git a/docs/source/resources.rst b/docs/source/resources.rst new file mode 100644 index 00000000..eb1580a5 --- /dev/null +++ b/docs/source/resources.rst @@ -0,0 +1,26 @@ +Resources +========= + +- `Catalyst Whitepaper `_ + + +Related 3rd Party APIs +^^^^^^^^^^^^^^^^^^^^^^ + +- `Zipline `_ is a Pythonic Algorithmic + Trading Library, and the project Catalyst forked off in the spring of 2017. +- `Quantopian `_ provides a platform for + freelance quantitative analysts develop, test, and use trading algorithms to + buy and sell securities. They aim to create a crowd-sourced hedge fund by + fostering their community of freelance traders. Quantopian's backtesting and + live-trading engine is powered by *Zipline*. +- `Pandas `_ is a Python + library providing high-performance, easy-to-use data structures and data + analysis tools. Catalyst relies heavily on pandas, and many API functions + return data as Pandas dataframes. +- `Numpy `_ is the fundamental + package for scientific computing with Python. Some of the data computation + that your algorithms will need, will be optimized leveraging Numpy. +- `Matplotlib `_ is a Python 2D + plotting library that many of examples rely on to plot the performance of + trading algorithms From 1c3deb648adc0a0d9134b5ff497c96d47f1273f7 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Tue, 31 Oct 2017 15:21:40 -0400 Subject: [PATCH 15/24] DOC: updated release notes for 0.3.4 and 0.3 --- docs/source/releases.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/source/releases.rst b/docs/source/releases.rst index 6be74731..7e6270db 100644 --- a/docs/source/releases.rst +++ b/docs/source/releases.rst @@ -2,6 +2,23 @@ Release Notes ============= +Version 0.3.4 +^^^^^^^^^^^^^ +**Release Date**: 2017-10-31 + +Bug Fixes +~~~~~~~~~ + +- Fixed issue with auto-ingestion of minute data +- Fixed issue with sell orders in backtesting +- Fixed data frequency issues with data.history() in backtesting + + +Build +~~~~~ + +- Added more unit tests + Version 0.3.3 ^^^^^^^^^^^^^ **Release Date**: 2017-10-26 @@ -57,6 +74,12 @@ Version 0.3 ^^^^^^^^^^^ **Release Date**: 2017-10-20 +- Standardized live and backtesting syntax +- Added a repository for historical data +- Added supported for multiple exchanges per algorithm +- Added a standardized dictionary of symbols for each exchange +- Added auto-ingestion of bundle data while backtesting +- Bug fixes Version 0.2.dev5 From 9a9e66b43dbb2de49c4c0a4b7ccb55d7fc870bca Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Tue, 31 Oct 2017 13:56:16 -0600 Subject: [PATCH 16/24] DOC: jupyter notebook, expanded welcome & improved install notes --- docs/source/index.rst | 1 + docs/source/install.rst | 193 +- docs/source/jupyter.rst | 15794 ++++++++++++++++++++++++++++++++++++++ docs/source/welcome.rst | 25 +- 4 files changed, 15929 insertions(+), 84 deletions(-) create mode 100644 docs/source/jupyter.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 6ce2f83e..82bbbd37 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -9,6 +9,7 @@ Table of Contents install beginner-tutorial + jupyter live-trading naming-convention videos diff --git a/docs/source/install.rst b/docs/source/install.rst index 687092dc..bf958cc9 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -1,6 +1,13 @@ Install ======= +To get started with Catalyst, you will need to install it in your computer. +Like any other piece of software, Catalyst has a number of dependencies +(other software on which it depends to run) that you will need to install, as +well. We recommend using a software named ``Conda`` that will manage all +these dependencies for you, and set up the environment needed to get you up +and running as easily as possible. See :ref:`Installing with Conda `. + Installing with ``pip`` ----------------------- @@ -9,19 +16,20 @@ Python package. There are two reasons for the additional complexity: -1. Catalyst ships several C extensions that require access to the CPython C API. - In order to build the C extensions, ``pip`` needs access to the CPython - header files for your Python installation. +1. Catalyst ships several C extensions that require access to the CPython C + API. In order to build the C extensions, ``pip`` needs access to the + CPython header files for your Python installation. 2. Catalyst depends on `numpy `_, the core library for numerical array computing in Python. Numpy depends on having the `LAPACK `_ linear algebra routines available. -Because LAPACK and the CPython headers are non-Python dependencies, the correct -way to install them varies from platform to platform. If you'd rather use a -single tool to install Python and non-Python dependencies, or if you're already -using `Anaconda `_ as your Python distribution, -you can skip to the :ref:`Installing with Conda ` section. +Because LAPACK and the CPython headers are non-Python dependencies, the +correctway to install them varies from platform to platform. If you'd rather +use a single tool to install Python and non-Python dependencies, or if you're +already using `Anaconda `_ as your Python +distribution, you can skip to the :ref:`Installing with Conda ` +section. Once you've installed the necessary additional dependencies (see below for your particular platform), you should be able to simply run @@ -34,8 +42,8 @@ If you use Python for anything other than Catalyst, we **strongly** recommend that you install in a `virtualenv `_. The `Hitchhiker's Guide to Python`_ provides an `excellent tutorial on virtualenv -`_. Here's a summarized -version: +`_. Here's a +summarized version: .. code-block:: bash @@ -44,9 +52,10 @@ version: $ source ./catalyst-venv/bin/activate $ pip install enigma-catalyst -Though not required by Catalyst directly, our example algorithms use matplotlib -to visually display the results of the trading algorithms. If you wish to run -any examples or use matplotlib during development, it can be installed using: +Though not required by Catalyst directly, our example algorithms use +matplotlib to visually display the results of the trading algorithms. If you +wish to run any examples or use matplotlib during development, it can be +installed using: .. code-block:: bash @@ -91,12 +100,12 @@ On `Arch Linux`_, you can acquire the additional dependencies via ``pacman``: OSX ~~~ -The version of Python shipped with OSX by default is generally out of date, and -has a number of quirks because it's used directly by the operating system. For -these reasons, many developers choose to install and use a separate Python +The version of Python shipped with OSX by default is generally out of date, +and has a number of quirks because it's used directly by the operating system. +For these reasons, many developers choose to install and use a separate Python installation. The `Hitchhiker's Guide to Python`_ provides an excellent guide -to `Installing Python on OSX `_, which -explains how to install Python with the `Homebrew`_ manager. +to `Installing Python on OSX `_, +which explains how to install Python with the `Homebrew`_ manager. Assuming you've installed Python with Homebrew, you'll also likely need the following brew packages: @@ -108,54 +117,59 @@ following brew packages: OSX + virtualenv + matplotlib ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -A note about using matplotlib in virtual enviroments on OSX: it may be necessary to run +A note about using matplotlib in virtual enviroments on OSX: it may be +necessary to run .. code-block:: bash echo "backend: TkAgg" > ~/.matplotlib/matplotlibrc -in order to override the default ``macosx`` backend for your system, which may not -be accessible from inside the virtual environment. This will allow Catalyst to open -matplotlib charts from within a virtual environment, which is useful for displaying -the performance of your backtests. To learn more about matplotlib backends, please refer to the +in order to override the default ``macosx`` backend for your system, which +may not be accessible from inside the virtual environment. This will allow +Catalyst to open matplotlib charts from within a virtual environment, which +is useful for displaying the performance of your backtests. To learn more +about matplotlib backends, please refer to the `matplotlib backend documentation `_. +.. _windows: Windows ~~~~~~~ In Windows, you will need the `Microsoft Visual C++ Compiler for Python 2.7 -`_. This package -contains the compiler and the set of system headers necessary for producing -binary wheels for Python 2.7 packages. If it's not already in your system, download -it and install it before proceeding to the next step. +`_. This +package contains the compiler and the set of system headers necessary for +producing binary wheels for Python 2.7 packages. If it's not already in your +system, download it and install it before proceeding to the next step. For windows, the easiest and best supported way to install Catalyst is to use :ref:`Conda `. -Some problems we have encountered installing the **Visual C++ Compiler** mentioned above -are as follows: +Some problems we have encountered installing the **Visual C++ Compiler** +mentioned above are as follows: - **The system administrator has set policies to prevent this installation**. - In some systems, there is a default *Windows Software Restriction* policy that - prevents the installation of some software packages like this one. You'll have - to change the Registry to circumvent this: + In some systems, there is a default *Windows Software Restriction* policy + that prevents the installation of some software packages like this one. + You'll have to change the Registry to circumvent this: - - Click ``Start``, and search for ``regedit`` and launch the ``Registry Editor`` + - Click ``Start``, and search for ``regedit`` and launch the + ``Registry Editor`` - Navigate to the following folder: ``HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Installer`` - If there is an entry for ``DisableMSI``, set the Value data to 0. - - If there is no such entry, click on the ``Edit`` menu -> ``New`` -> ``DWORD (32-bit) Value`` - and enter ``DisableMSI`` as the Name (and by default you get 0 as the Value Data) + - If there is no such entry, click on the ``Edit`` menu -> ``New`` -> + ``DWORD (32-bit) Value`` and enter ``DisableMSI`` as the Name (and by + default you get 0 as the Value Data) | - **The installer has encountered an unexpected error installing this package. This may indicate a problem with this package. The error code is 2503.** - We have observed this when trying to install a package without enough administrator - permissions. Even when you are logged in as an Administrator, you have to explictily - install this package with administrator privileges: + We have observed this when trying to install a package without enough + administrator permissions. Even when you are logged in as an Administrator, + you have to explictily install this package with administrator privileges: - Click ``Start`` and find ``CMD`` or ``Command Prompt`` - Right click on it and choose ``Run as administrator`` @@ -166,21 +180,22 @@ are as follows: Amazon Linux AMI ~~~~~~~~~~~~~~~~ -The packages ``pip`` and ``setuptools`` that come shipped by default are very outdated. -Thus, you first need to run: +The packages ``pip`` and ``setuptools`` that come shipped by default are very +outdated. Thus, you first need to run: .. code-block:: bash pip install --upgrade pip setuptools -The default installation is also missing the C and C++ compilers, which you install by: +The default installation is also missing the C and C++ compilers, which you +install by: .. code-block:: bash sudo yum install gcc gcc-c++ -Then you should follow the regular installation instructions outlined at the beginning -of this page. +Then you should follow the regular installation instructions outlined at the +beginning of this page. Troubleshooting ``pip`` Install @@ -205,17 +220,24 @@ Troubleshooting ``pip`` Install ---- **Issue**: - Package enigma-catalyst cannot still be found, even after upgrading pip (see above), with an error similar to: + Package enigma-catalyst cannot still be found, even after upgrading pip + (see above), with an error similar to: .. code-block:: bash Downloading/unpacking enigma-catalyst - Could not find a version that satisfies the requirement enigma-catalyst (from versions: 0.1.dev9, 0.2.dev2, 0.1.dev4, 0.1.dev5, 0.1.dev3, 0.2.dev1, 0.1.dev8, 0.1.dev6) + Could not find a version that satisfies the requirement enigma-catalyst + (from versions: 0.1.dev9, 0.2.dev2, 0.1.dev4, 0.1.dev5, 0.1.dev3, + 0.2.dev1, 0.1.dev8, 0.1.dev6) Cleaning up... No distributions matching the version for enigma-catalyst **Solution**: - In some systems (this error has been reported in Ubuntu), pip is configured to only find stable versions by default. Since Catalyst is in alpha version, pip cannot find a matching version that satisfies the installation requirements. The solution is to include the `--pre` flag to include pre-release and development versions: + In some systems (this error has been reported in Ubuntu), pip is configured + to only find stable versions by default. Since Catalyst is in alpha + version, pip cannot find a matching version that satisfies the installation + requirements. The solution is to include the `--pre` flag to include + pre-release and development versions: .. code-block:: bash @@ -251,10 +273,14 @@ Troubleshooting ``pip`` Install ---- **Issue**: - Installation fails with error: ``fatal error: Python.h: No such file or directory`` + Installation fails with error: + ``fatal error: Python.h: No such file or directory`` **Solution**: - Some systems (this issue has been reported in Ubuntu) require `python-dev` for the proper build and installation of package dependencies. The solution is to install python-dev, which is independent of the virtual environment. In Ubuntu, you would need to run: + Some systems (this issue has been reported in Ubuntu) require `python-dev` + for the proper build and installation of package dependencies. The solution + is to install python-dev, which is independent of the virtual environment. + In Ubuntu, you would need to run: .. code-block:: bash @@ -272,36 +298,41 @@ comes as part of Continuum Analytics' `Anaconda The primary advantage of using Conda over ``pip`` is that conda natively understands the complex binary dependencies of packages like ``numpy`` and -``scipy``. This means that ``conda`` can install Catalyst and its dependencies -without requiring the use of a second tool to acquire Catalyst's non-Python -dependencies. +``scipy``. This means that ``conda`` can install Catalyst and its +dependencies without requiring the use of a second tool to acquire Catalyst's +non-Python dependencies. + + For Windows, you will need the *Microsoft Visual C++ Compiler for Python + 2.7*. Follow the instructions on the :ref:`Windows` section and come back + here. For instructions on how to install ``conda``, see the `Conda Installation -Documentation `_. Alternatively, you -can install MiniConda, which is a smaller footprint (fewer packages and smaller -size) than its big brother Anaconda, but it still contains all the main packages -needed. To install MiniConda, you can follow these steps: +Documentation `_. Alternatively, +you can install MiniConda, which is a smaller footprint (fewer packages and +smaller size) than its big brother Anaconda, but it still contains all the +main packages needed. To install MiniConda, you can follow these steps: -1. Download `MiniConda `_. Select Python 2.7 for - your Operating System. -2. Install MiniConda. See the `Installation Instructions `_ - if you need help. -3. Ensure the correct installation by running ``conda list`` in a Terminal window, - which should print the list of packages installed with Conda. +1. Download `MiniConda `_. Select Python 2.7 + for your Operating System. +2. Install MiniConda. See the `Installation Instructions + `_ if you need help. +3. Ensure the correct installation by running ``conda list`` in a Terminal + window, which should print the list of packages installed with Conda. Once either Conda or MiniConda has been set up you can install Catalyst: -1. Download the file `python2.7-environment.yml `_. -2. Open a Terminal window and enter [``cd/dir``] into the directory where you saved - the above ``python2.7-environment.yml`` file. +1. Download the file `python2.7-environment.yml + `_. +2. Open a Terminal window and enter [``cd/dir``] into the directory where you + saved the above ``python2.7-environment.yml`` file. 3. Install using this file. This step can take about 5-10 minutes to install. .. code-block:: bash conda env create -f python2.7-environment.yml -4. Activate the environment (which you need to do every time you start a new session - to run Catalyst): +4. Activate the environment (which you need to do every time you start a new + session to run Catalyst): **Linux or OSX:** @@ -320,8 +351,9 @@ Congratulations! You now have Catalyst installed. Troubleshooting ``conda`` Install ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If the command ``conda env create -f python2.7-environment.yml`` in step 3 above failed -for any reason, you can try setting up the environment manually with the following steps: +If the command ``conda env create -f python2.7-environment.yml`` in step 3 +above failed for any reason, you can try setting up the environment manually +with the following steps: 1. Create the environment: @@ -352,20 +384,23 @@ for any reason, you can try setting up the environment manually with the followi Getting Help ------------ -If after following the instructions above, and going through the *Troubleshooting* sections, -you still experience problems installing Catalyst, you can seek additional help through the -following channels: +If after following the instructions above, and going through the +*Troubleshooting* sections, you still experience problems installing Catalyst, +you can seek additional help through the following channels: -- Join our `Discord community `_, and head over the #catalyst_dev - channel where many other users (as well as the project developers) hang out, and can assist - you with your particular issue. The more descriptive and the more information you can provide, - the easiest will be for others to help you out. +- Join our `Discord community `_, and head over + the #catalyst_dev channel where many other users (as well as the project + developers) hang out, and can assist you with your particular issue. The + more descriptive and the more information you can provide, the easiest will + be for others to help you out. - Report the problem you are experiencing on our - `GitHub repository `_ following the guidelines - provided therein. Before you do so, take a moment to browse through all `previous reported issues - `_ in the likely case - that someone else experienced that same issue before, and you get a hint on how to solve it. + `GitHub repository `_ + following the guidelines provided therein. Before you do so, take a moment + to browse through all `previous reported issues + `_ + in the likely case that someone else experienced that same issue before, + and you get a hint on how to solve it. .. _`Debian-derived`: https://www.debian.org/misc/children-distros diff --git a/docs/source/jupyter.rst b/docs/source/jupyter.rst new file mode 100644 index 00000000..4042da53 --- /dev/null +++ b/docs/source/jupyter.rst @@ -0,0 +1,15794 @@ +Catalyst & Jupyter Notebook +=========================== + +(Feel free to check out the actual Notebook file +`here `__) + +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 +^^^^^^^^^^^^^^^^^^ + +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 +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 + + 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 + + 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 + +
diff --git a/docs/source/welcome.rst b/docs/source/welcome.rst index 410d8c75..22bd37ff 100644 --- a/docs/source/welcome.rst +++ b/docs/source/welcome.rst @@ -1,9 +1,22 @@ .. image:: https://s3.amazonaws.com/enigmaco-docs/enigma-catalyst.jpg | -Catalyst is a data-driven crypto investment platform. It supports both -backtesting and live-trading in a number of different crypto-exchanges. -Catalyst empowers users to share and curate data and build profitable, -data-driven investment strategies. +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 ======== @@ -25,4 +38,6 @@ Features 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. \ No newline at end of file + 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 7ad047a432e5161a68427b53d0748c540aadf710 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Tue, 31 Oct 2017 19:20:39 -0400 Subject: [PATCH 17/24] BUG: fixed an issue with can_trade() --- catalyst/assets/_assets.pyx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/catalyst/assets/_assets.pyx b/catalyst/assets/_assets.pyx index bd67526e..19ddacd3 100644 --- a/catalyst/assets/_assets.pyx +++ b/catalyst/assets/_assets.pyx @@ -559,6 +559,20 @@ cdef class TradingPair(Asset): end_minute=self.end_minute ) + def is_exchange_open(self, dt_minute): + """ + Parameters + ---------- + dt_minute: pd.Timestamp (UTC, tz-aware) + The minute to check. + + Returns + ------- + boolean: whether the asset's exchange is open at the given minute. + """ + #TODO: consider implementing to spot holds + return True + cpdef __reduce__(self): """ Function used by pickle to determine how to serialize/deserialize this From e6ff7ee4fcfcd80cb4da7ee22a8058272f53d0fe Mon Sep 17 00:00:00 2001 From: fredfortier Date: Tue, 31 Oct 2017 21:40:12 -0400 Subject: [PATCH 18/24] BUG: reduced the commission and slippage values to account for lower volume transactions. These models are still simple approximations. More work required to closely model exchange fees. --- catalyst/exchange/exchange_blotter.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/catalyst/exchange/exchange_blotter.py b/catalyst/exchange/exchange_blotter.py index 8fcbf623..dc612891 100644 --- a/catalyst/exchange/exchange_blotter.py +++ b/catalyst/exchange/exchange_blotter.py @@ -11,10 +11,10 @@ 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: consider adjusting dynamically based on trading pair -DEFAULT_SLIPPAGE_SPREAD = 0.02 -DEFAULT_MAKER_FEE = 0.001 -DEFAULT_TAKER_FEE = 0.002 +# TODO: should work with set_commission and set_slippage +DEFAULT_SLIPPAGE_SPREAD = 0.0001 +DEFAULT_MAKER_FEE = 0.15 +DEFAULT_TAKER_FEE = 0.25 class TradingPairFeeSchedule(CommissionModel): @@ -100,13 +100,6 @@ class TradingPairFixedSlippage(SlippageModel): transaction = create_transaction( order, dt, execution_price, execution_volume ) - # transaction = Transaction( - # asset=order.asset, - # amount=abs(execution_volume), - # dt=dt, - # price=execution_price, - # order_id=order.id - # ) self._volume_for_bar += abs(transaction.amount) yield order, transaction From df357d23279a3ca7f30c5bc0fe1a4a83d0760d52 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Tue, 31 Oct 2017 21:43:24 -0400 Subject: [PATCH 19/24] BUG: reduced the commission and slippage values to account for lower volume transactions. These models are still simple approximations. More work required to closely model exchange fees. (fixing previous commit) --- catalyst/exchange/exchange_blotter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/catalyst/exchange/exchange_blotter.py b/catalyst/exchange/exchange_blotter.py index dc612891..365682c7 100644 --- a/catalyst/exchange/exchange_blotter.py +++ b/catalyst/exchange/exchange_blotter.py @@ -5,7 +5,7 @@ from catalyst.constants import LOG_LEVEL from catalyst.finance.blotter import Blotter from catalyst.finance.commission import CommissionModel from catalyst.finance.slippage import SlippageModel -from catalyst.finance.transaction import Transaction, create_transaction +from catalyst.finance.transaction import create_transaction log = Logger('exchange_blotter', level=LOG_LEVEL) @@ -13,8 +13,8 @@ log = Logger('exchange_blotter', level=LOG_LEVEL) # 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.15 -DEFAULT_TAKER_FEE = 0.25 +DEFAULT_MAKER_FEE = 0.0015 +DEFAULT_TAKER_FEE = 0.0025 class TradingPairFeeSchedule(CommissionModel): From 35677c553c7f04aff9504e64903659f2a78cd93c Mon Sep 17 00:00:00 2001 From: fredfortier Date: Tue, 31 Oct 2017 23:34:48 -0400 Subject: [PATCH 20/24] BUG: fixed issues with data frequencies in data.history() which was particularly noticeable in live mode and minor adjustments around the commission model --- catalyst/examples/simple_loop.py | 28 ++-- catalyst/exchange/bitfinex/bitfinex.py | 33 +++-- catalyst/exchange/bittrex/bittrex.py | 22 ++-- catalyst/exchange/bundle_utils.py | 23 ++-- catalyst/exchange/exchange.py | 122 ++++++++++++++--- catalyst/exchange/exchange_data_portal.py | 4 +- catalyst/exchange/exchange_errors.py | 8 ++ catalyst/exchange/exchange_utils.py | 81 +++++++----- catalyst/exchange/poloniex/poloniex.py | 36 ++--- catalyst/support/rodrigo_1.py | 153 ++++++++++++++++++++++ docs/source/releases.rst | 1 + tests/exchange/test_bitfinex.py | 4 +- tests/exchange/test_bittrex.py | 4 +- tests/exchange/test_bundle.py | 16 +-- tests/exchange/test_poloniex.py | 8 +- 15 files changed, 398 insertions(+), 145 deletions(-) create mode 100644 catalyst/support/rodrigo_1.py diff --git a/catalyst/examples/simple_loop.py b/catalyst/examples/simple_loop.py index 52400485..9f02e5ac 100644 --- a/catalyst/examples/simple_loop.py +++ b/catalyst/examples/simple_loop.py @@ -1,13 +1,13 @@ import talib - import pandas as pd + from catalyst import run_algorithm from catalyst.api import symbol def initialize(context): print('initializing') - context.asset = symbol('burst_btc') + context.asset = symbol('eth_btc') def handle_data(context, data): @@ -16,22 +16,22 @@ def handle_data(context, data): price = data.current(context.asset, 'close') print('got price {price}'.format(price=price)) - prices = data.history( - context.asset, - fields='price', - bar_count=15, - frequency='1d' - ) - rsi = talib.RSI(prices.values, timeperiod=14)[-1] - print('got rsi: {}'.format(rsi)) + # prices = data.history( + # context.asset, + # fields='price', + # bar_count=20, + # frequency='1T' + # ) + # rsi = talib.RSI(prices.values, timeperiod=14)[-1] + # print('got rsi: {}'.format(rsi)) pass run_algorithm( capital_base=250, - start=pd.to_datetime('2017-08-01', utc=True), - end=pd.to_datetime('2017-9-30', utc=True), - data_frequency='minute', + start=pd.to_datetime('2017-1-1', utc=True), + end=pd.to_datetime('2017-10-22', utc=True), + data_frequency='daily', initialize=initialize, handle_data=handle_data, analyze=None, @@ -43,7 +43,7 @@ run_algorithm( # initialize=initialize, # handle_data=handle_data, # analyze=None, -# exchange_name='bitfinex', +# exchange_name='bittrex', # live=True, # algo_namespace='simple_loop', # base_currency='eth', diff --git a/catalyst/exchange/bitfinex/bitfinex.py b/catalyst/exchange/bitfinex/bitfinex.py index ea243a1f..4c878e04 100644 --- a/catalyst/exchange/bitfinex/bitfinex.py +++ b/catalyst/exchange/bitfinex/bitfinex.py @@ -240,7 +240,7 @@ class Bitfinex(Exchange): # TODO: fetch account data and keep in cache return None - def get_candles(self, data_frequency, assets, bar_count=None, + def get_candles(self, freq, assets, bar_count=None, start_dt=None, end_dt=None): """ Retrieve OHLVC candles from Bitfinex @@ -259,39 +259,36 @@ class Bitfinex(Exchange): 'retrieving {bars} {freq} candles on {exchange} from ' '{end_dt} for markets {symbols}, '.format( bars=bar_count, - freq=data_frequency, + freq=freq, exchange=self.name, end_dt=end_dt, symbols=get_symbols_string(assets) ) ) - freq_match = re.match(r'([0-9].*)(m|h|d)', data_frequency, re.M | re.I) + allowed_frequencies = ['1T', '5T', '15T', '30T', '60T', '180T', + '360T', '720T', '1D', '7D', '14D', '30D'] + if freq not in allowed_frequencies: + raise InvalidHistoryFrequencyError(frequency=freq) + + freq_match = re.match(r'([0-9].*)(T|H|D)', freq, re.M | re.I) if freq_match: number = int(freq_match.group(1)) unit = freq_match.group(2) - if unit == 'd': - converted_unit = 'D' + if unit == 'T': + if number in [60, 180, 360, 720]: + number = number / 60 + converted_unit = 'h' + else: + converted_unit = 'm' else: converted_unit = unit frequency = '{}{}'.format(number, converted_unit) - allowed_frequencies = ['1m', '5m', '15m', '30m', '1h', '3h', '6h', - '12h', '1D', '7D', '14D', '1M'] - if frequency not in allowed_frequencies: - raise InvalidHistoryFrequencyError( - frequency=data_frequency - ) - elif data_frequency == 'minute': - frequency = '1m' - elif data_frequency == 'daily': - frequency = '1D' else: - raise InvalidHistoryFrequencyError( - frequency=data_frequency - ) + raise InvalidHistoryFrequencyError(frequency=freq) # Making sure that assets are iterable asset_list = [assets] if isinstance(assets, TradingPair) else assets diff --git a/catalyst/exchange/bittrex/bittrex.py b/catalyst/exchange/bittrex/bittrex.py index 1bbfc68b..6c490a46 100644 --- a/catalyst/exchange/bittrex/bittrex.py +++ b/catalyst/exchange/bittrex/bittrex.py @@ -210,14 +210,14 @@ class Bittrex(Exchange): error=status['message'] ) - def get_candles(self, data_frequency, assets, bar_count=None, + def get_candles(self, freq, assets, bar_count=None, start_dt=None, end_dt=None): """ Supported Intervals ------------------- day, oneMin, fiveMin, thirtyMin, hour - :param data_frequency: + :param freq: :param assets: :param bar_count: :param start_dt @@ -233,28 +233,25 @@ class Bittrex(Exchange): 'retrieving {bars} {freq} candles on {exchange} from ' '{end_dt} for markets {symbols}, '.format( bars=bar_count, - freq=data_frequency, + freq=freq, exchange=self.name, end_dt=end_dt, symbols=get_symbols_string(assets) ) ) - data_frequency = data_frequency.lower() - if data_frequency == 'minute' or data_frequency == '1m': + if freq == '1T': frequency = 'oneMin' - elif data_frequency == '5m': + elif freq == '5T': frequency = 'fiveMin' - elif data_frequency == '30m': + elif freq == '30T': frequency = 'thirtyMin' - elif data_frequency == '1h': + elif freq == '60T': frequency = 'hour' - elif data_frequency == 'daily' or data_frequency == '1d': + elif freq == '1D': frequency = 'day' else: - raise InvalidHistoryFrequencyError( - frequency=data_frequency - ) + raise InvalidHistoryFrequencyError(frequency=freq) # Making sure that assets are iterable asset_list = [assets] if isinstance(assets, TradingPair) else assets @@ -297,6 +294,7 @@ class Bittrex(Exchange): if bar_count is None: ohlc_map[asset] = ohlc_from_candle(ordered_candles[0]) else: + # TODO: optimize ohlc_bars = [] for candle in ordered_candles[:bar_count]: ohlc = ohlc_from_candle(candle) diff --git a/catalyst/exchange/bundle_utils.py b/catalyst/exchange/bundle_utils.py index 9a0eb3c2..9d357e23 100644 --- a/catalyst/exchange/bundle_utils.py +++ b/catalyst/exchange/bundle_utils.py @@ -71,25 +71,18 @@ def get_delta(periods, data_frequency): if data_frequency == 'minute' else timedelta(days=periods) -def get_periods_range(start_dt, end_dt, data_frequency): - freq = 'T' if data_frequency == 'minute' else 'D' +def get_periods_range(start_dt, end_dt, freq): + if freq == 'minute': + freq = 'T' + + elif freq == 'daily': + freq = 'D' return pd.date_range(start_dt, end_dt, freq=freq) -def get_periods(start_dt, end_dt, data_frequency): - delta = end_dt - start_dt - - if data_frequency == 'minute': - delta_periods = delta.total_seconds() / 60 - - elif data_frequency == 'daily': - delta_periods = delta.total_seconds() / 60 / 60 / 24 - - else: - raise ValueError('frequency not supported') - - return int(delta_periods) +def get_periods(start_dt, end_dt, freq): + return len(get_periods_range(start_dt, end_dt, freq)) def get_start_dt(end_dt, bar_count, data_frequency): diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index 3640bbe3..91fc6733 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -12,11 +12,12 @@ from logbook import Logger 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_delta, get_periods, get_periods_range from catalyst.exchange.exchange_bundle import ExchangeBundle from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \ InvalidOrderStyle, BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \ - InvalidHistoryFrequencyError, PricingDataNotLoadedError + InvalidHistoryFrequencyError, PricingDataNotLoadedError, \ + NoDataAvailableOnExchange from catalyst.exchange.exchange_execution import ExchangeStopLimitOrder, \ ExchangeLimitOrder, ExchangeStopOrder from catalyst.exchange.exchange_portfolio import ExchangePortfolio @@ -24,6 +25,7 @@ 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 +from catalyst.utils.deprecate import deprecated log = Logger('Exchange', level=LOG_LEVEL) @@ -71,6 +73,15 @@ class Exchange: def time_skew(self): pass + def is_open(self, dt): + """ + Is the exchange open? + :param dt: + :return: + """ + # TODO: implement for each exchange. + return True + def ask_request(self): """ Asks permission to issue a request to the exchange. @@ -364,7 +375,8 @@ class Exchange: ) ) - ohlc = self.get_candles(data_frequency, asset) + freq = '1T' if data_frequency == 'minute' else '1D' + ohlc = self.get_candles(freq, asset) if field not in ohlc: raise KeyError('Invalid column: %s' % field) @@ -388,17 +400,84 @@ class Exchange: dates = [candle['last_traded'] for candle in candles] values = [candle[field] for candle in candles] - - periods = self.bundle.get_calendar_periods_range( - start_dt, end_dt, data_frequency - ) series = pd.Series(values, index=dates) + periods = get_periods_range( + start_dt, end_dt, data_frequency + ) # TODO: ensure that this working as expected, if not use fillna - series.reindex(periods, method='ffill', fill_value=previous_value) + series = series.reindex( + periods, + method='ffill', + fill_value=previous_value, + ) return series + @deprecated + def get_history_window_direct(self, + assets, + end_dt, + bar_count, + frequency, + field, + data_frequency=None, + ffill=True): + + """ + Public API method that returns a dataframe containing the requested + history window. Data is fully adjusted. + + Parameters + ---------- + assets : list of catalyst.data.Asset objects + The assets whose data is desired. + + end_dt: not applicable to cryptocurrencies + + bar_count: int + The number of bars desired. + + frequency: string + "1d" or "1m" + + field: string + The desired field of the asset. + + data_frequency: string + 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'. + + Returns + ------- + A dataframe containing the requested data. + """ + start_dt = get_start_dt(end_dt, bar_count, data_frequency) + + # The get_history method supports multiple asset + candles = self.get_candles( + data_frequency=frequency, + assets=assets, + bar_count=bar_count, + start_dt=start_dt, + end_dt=end_dt + ) + candle_series = self.get_series_from_candles( + candles=candles, + start_dt=start_dt, + end_dt=end_dt, + data_frequency=frequency, + field=field, + ) + + df = pd.DataFrame(candle_series) + return df + def get_history_window(self, assets, end_dt, @@ -442,7 +521,7 @@ class Exchange: A dataframe containing the requested data. """ - candle_size, unit, data_frequency = get_frequency( + freq, candle_size, unit, data_frequency = get_frequency( frequency, data_frequency ) adj_bar_count = candle_size * bar_count @@ -454,7 +533,7 @@ class Exchange: field=field, data_frequency=data_frequency ) - except PricingDataNotLoadedError: + except (PricingDataNotLoadedError, NoDataAvailableOnExchange): series = dict() for asset in assets: @@ -467,12 +546,14 @@ class Exchange: series[asset].index[-1] + get_delta(1, data_frequency) \ if asset in series else start_dt - trailing_bar_count = \ - get_periods(trailing_dt, end_dt, data_frequency) - # The get_history method supports multiple asset + # Use the original frequency to let each api optimize + # the size of result sets + trailing_bar_count = get_periods( + trailing_dt, end_dt, freq + ) candles = self.get_candles( - data_frequency=data_frequency, + freq=freq, assets=asset, bar_count=trailing_bar_count, start_dt=start_dt, @@ -482,6 +563,8 @@ class Exchange: last_value = series[asset].iloc(0) if asset in series \ else np.nan + # Create a series with the common data_frequency, ffill + # missing values candle_series = self.get_series_from_candles( candles=candles, start_dt=trailing_dt, @@ -497,7 +580,9 @@ class Exchange: else: series[asset] = candle_series - df = resample_history_df(pd.DataFrame(series), candle_size, field) + df = resample_history_df(pd.DataFrame(series), freq, field) + # TODO: consider this more carefully + df.dropna(inplace=True) return df @@ -708,13 +793,14 @@ class Exchange: pass @abstractmethod - def get_candles(self, data_frequency, assets, bar_count=None, + def get_candles(self, freq, assets, bar_count=None, start_dt=None, end_dt=None): """ Retrieve OHLCV candles for the given assets - :param data_frequency: - The candle frequency: minute or daily + :param freq: + The frequency alias per convention: + http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases :param assets: list[TradingPair] The targeted assets. :param bar_count: diff --git a/catalyst/exchange/exchange_data_portal.py b/catalyst/exchange/exchange_data_portal.py index 8d2cf997..f8d93288 100644 --- a/catalyst/exchange/exchange_data_portal.py +++ b/catalyst/exchange/exchange_data_portal.py @@ -303,7 +303,7 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase): """ bundle = self.exchange_bundles[exchange.name] # type: ExchangeBundle - candle_size, unit, data_frequency = get_frequency( + freq, candle_size, unit, data_frequency = get_frequency( frequency, data_frequency ) adj_bar_count = candle_size * bar_count @@ -317,7 +317,7 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase): algo_end_dt=self._last_available_session, ) - df = resample_history_df(pd.DataFrame(series), candle_size, field) + df = resample_history_df(pd.DataFrame(series), freq, field) return df def get_exchange_spot_value(self, diff --git a/catalyst/exchange/exchange_errors.py b/catalyst/exchange/exchange_errors.py index 6cd55014..00e53395 100644 --- a/catalyst/exchange/exchange_errors.py +++ b/catalyst/exchange/exchange_errors.py @@ -86,6 +86,14 @@ class AlgoPickleNotFound(ZiplineError): ).strip() +class InvalidHistoryFrequencyAlias(ZiplineError): + msg = ( + 'Invalid frequency alias {freq}. Valid suffixes are M (minute) ' + 'and D (day). For example, these aliases would be valid ' + '1M, 5M, 1D.' + ).strip() + + class InvalidHistoryFrequencyError(ZiplineError): msg = ( 'Frequency {frequency} not supported by the exchange.' diff --git a/catalyst/exchange/exchange_utils.py b/catalyst/exchange/exchange_utils.py index 3bd35223..812c7776 100644 --- a/catalyst/exchange/exchange_utils.py +++ b/catalyst/exchange/exchange_utils.py @@ -10,7 +10,7 @@ from datetime import date, datetime import pandas as pd from catalyst.exchange.exchange_errors import ExchangeSymbolsNotFound, \ - InvalidHistoryFrequencyError + InvalidHistoryFrequencyError, InvalidHistoryFrequencyAlias from catalyst.utils.paths import data_root, ensure_directory, \ last_modified_time @@ -313,50 +313,61 @@ def get_common_assets(exchanges): def get_frequency(freq, data_frequency): - if freq == 'daily': - freq = '1d' - elif freq == 'minute': - freq = '1m' + if freq == 'minute': + unit = 'T' + candle_size = 1 - freq_match = re.match(r'([0-9].*)(m|M|d|D)', freq, re.M | re.I) - if freq_match: - candle_size = int(freq_match.group(1)) - unit = freq_match.group(2) + elif freq == 'daily': + unit = 'D' + candle_size = 1 else: - raise InvalidHistoryFrequencyError(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': + alias = '{}D'.format(candle_size) + if data_frequency == 'minute': data_frequency = 'daily' - elif unit.lower() == 'm': + elif unit.lower() == 'm' or unit == 'T': + alias = '{}T'.format(candle_size) + if data_frequency == 'daily': data_frequency = 'minute' - else: - raise InvalidHistoryFrequencyError(freq) - - return candle_size, unit, data_frequency - - -def resample_history_df(df, candle_size, field): - if candle_size > 1: - if field == 'open': - agg = 'first' - elif field == 'high': - agg = 'max' - elif field == 'low': - agg = 'min' - elif field == 'close': - agg = 'last' - elif field == 'volume': - agg = 'sum' - else: - raise ValueError('Invalid field.') - - # TODO: pad with nan? - return df.resample('{}T'.format(candle_size)).agg(agg) + # elif unit.lower() == 'h': + # candle_size = candle_size * 60 + # + # alias = '{}T'.format(candle_size) + # if data_frequency == 'daily': + # data_frequency = 'minute' else: - return df + raise InvalidHistoryFrequencyAlias(freq=freq) + + return alias, candle_size, unit, data_frequency + + +def resample_history_df(df, freq, field): + if field == 'open': + agg = 'first' + elif field == 'high': + agg = 'max' + elif field == 'low': + agg = 'min' + elif field == 'close': + agg = 'last' + elif field == 'volume': + agg = 'sum' + else: + raise ValueError('Invalid field.') + + return df.resample(freq).agg(agg) diff --git a/catalyst/exchange/poloniex/poloniex.py b/catalyst/exchange/poloniex/poloniex.py index 468463b0..65c397c7 100644 --- a/catalyst/exchange/poloniex/poloniex.py +++ b/catalyst/exchange/poloniex/poloniex.py @@ -1,3 +1,4 @@ +import calendar import json import json import time @@ -171,12 +172,12 @@ class Poloniex(Exchange): # TODO: fetch account data and keep in cache return None - def get_candles(self, data_frequency, assets, bar_count=None, + def get_candles(self, freq, assets, bar_count=None, start_dt=None, end_dt=None): """ Retrieve OHLVC candles from Poloniex - :param data_frequency: + :param freq: :param assets: :param bar_count: :return: @@ -193,31 +194,33 @@ class Poloniex(Exchange): 'retrieving {bars} {freq} candles on {exchange} from ' '{end_dt} for markets {symbols}, '.format( bars=bar_count, - freq=data_frequency, + freq=freq, exchange=self.name, end_dt=end_dt, symbols=get_symbols_string(assets) ) ) - if data_frequency == '5m': + if freq == '1T' and (bar_count == 1 or bar_count is None): + # TODO: use the order book instead + # We use the 5m to fetch the last bar frequency = 300 - elif data_frequency == '15m': + elif freq == '5T': + frequency = 300 + elif freq == '15T': frequency = 900 - elif data_frequency == '30m': + elif freq == '30T': frequency = 1800 - elif data_frequency == '2h': + elif freq == '120T': frequency = 7200 - elif data_frequency == '4h': + elif freq == '240T': frequency = 14400 - elif data_frequency == '1D' or data_frequency == 'daily': + elif freq == '1D': frequency = 86400 else: # Poloniex does not offer 1m data candles # It is likely to error out there frequently - raise InvalidHistoryFrequencyError( - frequency=data_frequency - ) + raise InvalidHistoryFrequencyError(frequency=freq) # Making sure that assets are iterable asset_list = [assets] if isinstance(assets, TradingPair) else assets @@ -225,15 +228,18 @@ class Poloniex(Exchange): for asset in asset_list: - end = int(time.mktime(end_dt.timetuple())) + # TODO: what's wrong with this? + # end = int(time.mktime(end_dt.timetuple())) + end = int(time.time()) if bar_count is None: start = end - 2 * frequency else: start = end - bar_count * frequency try: - response = self.api.returnchartdata(self.get_symbol(asset), - frequency, start, end) + response = self.api.returnchartdata( + self.get_symbol(asset), frequency, start, end + ) except Exception as e: raise ExchangeRequestError(error=e) diff --git a/catalyst/support/rodrigo_1.py b/catalyst/support/rodrigo_1.py new file mode 100644 index 00000000..ec32c617 --- /dev/null +++ b/catalyst/support/rodrigo_1.py @@ -0,0 +1,153 @@ +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/docs/source/releases.rst b/docs/source/releases.rst index 7e6270db..ee220190 100644 --- a/docs/source/releases.rst +++ b/docs/source/releases.rst @@ -12,6 +12,7 @@ Bug Fixes - Fixed issue with auto-ingestion of minute data - Fixed issue with sell orders in backtesting - Fixed data frequency issues with data.history() in backtesting +- Fixed an issue with can_trade() Build diff --git a/tests/exchange/test_bitfinex.py b/tests/exchange/test_bitfinex.py index 94968e9e..194422f5 100644 --- a/tests/exchange/test_bitfinex.py +++ b/tests/exchange/test_bitfinex.py @@ -8,7 +8,7 @@ from catalyst.finance.execution import (LimitOrder) log = Logger('test_bitfinex') -class TestBitfinexTestCase(BaseExchangeTestCase): +class TestBitfinex(BaseExchangeTestCase): @classmethod def setup(self): log.info('creating bitfinex object') @@ -48,7 +48,7 @@ class TestBitfinexTestCase(BaseExchangeTestCase): def test_get_candles(self): log.info('retrieving candles') ohlcv_neo = self.exchange.get_candles( - data_frequency='1m', + freq='1T', assets=self.exchange.get_asset('neo_btc') ) pass diff --git a/tests/exchange/test_bittrex.py b/tests/exchange/test_bittrex.py index 779c2a08..bf17970d 100644 --- a/tests/exchange/test_bittrex.py +++ b/tests/exchange/test_bittrex.py @@ -52,13 +52,13 @@ class TestBittrex(BaseExchangeTestCase): def test_get_candles(self): log.info('retrieving candles') ohlcv_neo = self.exchange.get_candles( - data_frequency='5m', + 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( - data_frequency='1d', + freq='1D', assets=[ self.exchange.get_asset('neo_btc'), self.exchange.get_asset('ubq_btc') diff --git a/tests/exchange/test_bundle.py b/tests/exchange/test_bundle.py index 060592ec..2180dd03 100644 --- a/tests/exchange/test_bundle.py +++ b/tests/exchange/test_bundle.py @@ -126,9 +126,9 @@ class TestExchangeBundle: # data_frequency = 'daily' # include_symbols = 'neo_btc,bch_btc,eth_btc' - exchange_name = 'bittrex' + exchange_name = 'poloniex' data_frequency = 'daily' - include_symbols = 'wings_eth' + include_symbols = 'eth_btc' start = pd.to_datetime('2017-1-1', utc=True) end = pd.to_datetime('2017-10-16', utc=True) @@ -140,10 +140,10 @@ class TestExchangeBundle: log.info('ingesting exchange bundle {}'.format(exchange_name)) exchange_bundle.ingest( data_frequency=data_frequency, - include_symbols=None, + include_symbols=include_symbols, exclude_symbols=None, - start=None, - end=None, + start=start, + end=end, show_progress=True ) @@ -342,7 +342,7 @@ class TestExchangeBundle: assets=assets, end_dt=end_dt, bar_count=bar_count, - data_frequency='minute' + freq='1T' ) start_dt = get_start_dt(end_dt, bar_count, data_frequency) @@ -392,7 +392,7 @@ class TestExchangeBundle: start_dt=start_dt, end_dt=end_dt, bar_count=bar_count, - data_frequency=data_frequency + freq='1T' ) writer = bundle.get_writer(start_dt, end_dt, data_frequency) @@ -437,7 +437,7 @@ class TestExchangeBundle: exchange = get_exchange(exchange_name) bundle = ExchangeBundle(exchange) - asset = exchange.get_asset('xmr_btc') + asset = exchange.get_asset('eth_btc') path = get_bcolz_chunk( exchange_name=exchange.name, diff --git a/tests/exchange/test_poloniex.py b/tests/exchange/test_poloniex.py index 4a883701..b2ad56c3 100644 --- a/tests/exchange/test_poloniex.py +++ b/tests/exchange/test_poloniex.py @@ -8,7 +8,7 @@ from catalyst.exchange.exchange_utils import get_exchange_auth log = Logger('test_poloniex') -class TestPoloniexTestCase(BaseExchangeTestCase): +class TestPoloniex(BaseExchangeTestCase): @classmethod def setup(self): print ('creating poloniex object') @@ -52,11 +52,11 @@ class TestPoloniexTestCase(BaseExchangeTestCase): def test_get_candles(self): log.info('retrieving candles') ohlcv_neo = self.exchange.get_candles( - data_frequency='5m', - assets=self.exchange.get_asset('neos_btc') + freq='5T', + assets=self.exchange.get_asset('eth_btc') ) ohlcv_neo_ubq = self.exchange.get_candles( - data_frequency='5m', + freq='5T', assets=[ self.exchange.get_asset('neos_btc'), self.exchange.get_asset('via_btc') From 5b6bbacab0e81a13bb033fba2fe2d4fed2ccb369 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Wed, 1 Nov 2017 21:31:51 -0400 Subject: [PATCH 21/24] DOC: updating the code docstrings --- catalyst/exchange/exchange.py | 199 +++++++++++++++------- catalyst/exchange/exchange_algorithm.py | 118 ++++++++++++- catalyst/exchange/exchange_bcolz.py | 28 +-- catalyst/exchange/exchange_bundle.py | 157 +++++++++++------ catalyst/exchange/exchange_data_portal.py | 103 +++++++---- catalyst/exchange/exchange_execution.py | 52 ++++-- catalyst/exchange/exchange_portfolio.py | 34 +++- catalyst/exchange/exchange_utils.py | 29 +++- 8 files changed, 527 insertions(+), 193 deletions(-) diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index 91fc6733..b1ad3fb9 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -1,5 +1,4 @@ import abc -import re from abc import ABCMeta, abstractmethod, abstractproperty from datetime import timedelta from time import sleep @@ -16,7 +15,7 @@ from catalyst.exchange.bundle_utils import get_start_dt, \ from catalyst.exchange.exchange_bundle import ExchangeBundle from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \ InvalidOrderStyle, BaseCurrencyNotFoundError, SymbolNotFoundOnExchange, \ - InvalidHistoryFrequencyError, PricingDataNotLoadedError, \ + PricingDataNotLoadedError, \ NoDataAvailableOnExchange from catalyst.exchange.exchange_execution import ExchangeStopLimitOrder, \ ExchangeLimitOrder, ExchangeStopOrder @@ -53,9 +52,11 @@ class Exchange: @property def portfolio(self): """ - Return the Portfolio + The exchange portfolio - :return: + Returns + ------- + ExchangePortfolio """ if self._portfolio is None: self._portfolio = ExchangePortfolio( @@ -75,9 +76,16 @@ class Exchange: def is_open(self, dt): """ - Is the exchange open? - :param dt: - :return: + Is the exchange open + + Parameters + ---------- + dt: Timestamp + + Returns + ------- + bool + """ # TODO: implement for each exchange. return True @@ -90,7 +98,9 @@ class Exchange: The application will pause if the maximum requests per minute permitted by the exchange is exceeded. - :return boolean: + Returns + ------- + bool """ now = pd.Timestamp.utcnow() @@ -122,10 +132,16 @@ class Exchange: def get_symbol(self, asset): """ - Get the exchange specific symbol of the given asset. + The the exchange specific symbol of the specified market. + + Parameters + ---------- + asset: TradingPair + + Returns + ------- + str - :param asset: Asset - :return: symbol: str """ symbol = None @@ -143,17 +159,34 @@ class Exchange: """ Get a list of symbols corresponding to each given asset. - :param assets: Asset[] - :return: + Parameters + ---------- + assets: list[TradingPair] + + Returns + ------- + list[str] + """ symbols = [] - for asset in assets: symbols.append(self.get_symbol(asset)) return symbols def get_assets(self, symbols=None): + """ + The list of markets for the specified symbols. + + Parameters + ---------- + symbols: list[str] + + Returns + ------- + list[TradingPair] + + """ assets = [] if symbols is not None: @@ -168,9 +201,16 @@ class Exchange: def get_asset(self, symbol): """ - Find an Asset on the current exchange based on its Catalyst symbol - :param symbol: the [target]_[base] currency pair symbol - :return: Asset + The market for the specified symbol. + + Parameters + ---------- + symbol: str + + Returns + ------- + TradingPair + """ asset = None @@ -201,7 +241,6 @@ class Exchange: currency pair symbol. The universal symbol is contained in the 'symbol' attribute of each asset. - Notes ----- The sid of each asset is calculated based on a numeric hash of the @@ -210,8 +249,8 @@ class Exchange: This method can be overridden if an exchange offers equivalent data via its api. - """ + """ symbol_map = self.fetch_symbol_map() for exchange_symbol in symbol_map: asset = symbol_map[exchange_symbol] @@ -272,8 +311,10 @@ class Exchange: For each executed order found, create a transaction and apply to the Portfolio. - :return: - transactions: Transaction[] + Returns + ------- + list[Transaction] + """ transactions = list() if self.portfolio.open_orders: @@ -390,14 +431,20 @@ class Exchange: """ Get a series of field data for the specified candles. - :param candles: - :param start_dt: - :param end_dt: - :param field: - :param previous_value: - :return: - """ + Parameters + ---------- + candles: list[dict[str, float]] + start_dt: datetime + end_dt: datetime + data_frequency: str + field: str + previous_value: float + Returns + ------- + Series + + """ dates = [candle['last_traded'] for candle in candles] values = [candle[field] for candle in candles] series = pd.Series(values, index=dates) @@ -430,10 +477,11 @@ class Exchange: Parameters ---------- - assets : list of catalyst.data.Asset objects + assets : list[TradingPair] The assets whose data is desired. - end_dt: not applicable to cryptocurrencies + end_dt: datetime + The date of the last bar bar_count: int The number of bars desired. @@ -493,10 +541,11 @@ class Exchange: Parameters ---------- - assets : list of catalyst.data.Asset objects + assets : list[TradingPair] The assets whose data is desired. - end_dt: not applicable to cryptocurrencies + end_dt: datetime + The date of the last bar. bar_count: int The number of bars desired. @@ -518,9 +567,10 @@ class Exchange: Returns ------- - A dataframe containing the requested data. - """ + DataFrame + A dataframe containing the requested data. + """ freq, candle_size, unit, data_frequency = get_frequency( frequency, data_frequency ) @@ -591,7 +641,6 @@ class Exchange: Update the portfolio cash and position balances based on the latest ticker prices. - :return: """ log.debug('synchronizing portfolio with exchange {}'.format(self.name)) balances = self.get_balances() @@ -635,16 +684,20 @@ class Exchange: Parameters ---------- - asset : Asset + asset : TradingPair The asset that this order is for. + amount : int The amount of shares to order. If ``amount`` is positive, this is the number of shares to buy or cover. If ``amount`` is negative, this is the number of shares to sell or short. + limit_price : float, optional The limit price for the order. + stop_price : float, optional The stop price for the order. + style : ExecutionStyle, optional The execution style for the order. @@ -669,6 +722,7 @@ class Exchange: :class:`catalyst.finance.execution.ExecutionStyle` :func:`catalyst.api.order_value` :func:`catalyst.api.order_percent` + """ if amount == 0: log.warn('skipping order amount of 0') @@ -718,8 +772,12 @@ class Exchange: @abstractmethod def get_balances(self): """ - Retrieve wallet balances for the exchange - :return balances: A dict of currency => available balance + Retrieve wallet balances for the exchange. + + Returns + ------- + dict[TradingPair, float] + """ pass @@ -728,17 +786,25 @@ class Exchange: """ Place an order on the exchange. - :param asset : Asset - The asset that this order is for. - :param amount : int + Parameters + ---------- + asset: TradingPair + The target market. + + amount: float The amount of shares to order. If ``amount`` is positive, this is the number of shares to buy or cover. If ``amount`` is negative, this is the number of shares to sell or short. - :param style : ExecutionStyle - The execution style for the order. - :param is_buy: boolean + + is_buy: bool Is it a buy order? - :return: + + style: ExecutionStyle + + Returns + ------- + Order + """ pass @@ -798,19 +864,27 @@ class Exchange: """ Retrieve OHLCV candles for the given assets - :param freq: + Parameters + ---------- + freq: str The frequency alias per convention: http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases - :param assets: list[TradingPair] + + assets: list[TradingPair] The targeted assets. - :param bar_count: + + bar_count: int The number of bar desired. (default 1) - :param end_dt: datetime, optional + + end_dt: datetime, optional The last bar date. - :param start_dt: datetime, optional + + start_dt: datetime, optional The first bar date. - :return dict[TradingPair, dict[str, Object]]: OHLCV data + Returns + ------- + dict[TradingPair, dict[str, Object]] A dictionary of OHLCV candles. Each TradingPair instance is mapped to a list of dictionaries with this structure: open: float @@ -830,8 +904,14 @@ class Exchange: """ Retrieve current tick data for the given assets - :param assets: - :return: + Parameters + ---------- + assets: list[TradingPair] + + Returns + ------- + list[dict[str, float] + """ pass @@ -839,7 +919,6 @@ class Exchange: def get_account(self): """ Retrieve the account parameters. - :return: """ pass @@ -848,11 +927,15 @@ class Exchange: """ Retrieve the the orderbook for the given trading pair. - :param asset: TradingPair - :param order_type: str + Parameters + ---------- + asset: TradingPair + order_type: str The type of orders: bid, ask or all - :param limit + limit: int - :return: + Returns + ------- + list[dict[str, float] """ pass diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index 6650fda2..c05e559f 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -127,7 +127,13 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm): """ Creates a dictionary representing the state of the tracker. + Parameters + ---------- + start_dt: datetime + end_dt: datetime + Notes + ----- I rewrote this in an attempt to better control the stats. I don't want things to happen magically through complex logic pertaining to backtesting. @@ -296,6 +302,18 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): self.exchange.minute_reader = BcolzMinuteBarReader(root) def signal_handler(self, signal, frame): + """ + Handles the keyboard interruption signal. + + Parameters + ---------- + signal + frame + + Returns + ------- + + """ self.is_running = False if self._analyze is None: @@ -384,7 +402,11 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): """ We skip the entire performance tracker business and update the portfolio directly. - :return: + + Returns + ------- + ExchangePortfolio + """ # TODO: build cumulative portfolio return self.perf_tracker.get_portfolio(False) @@ -450,6 +472,17 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): ) def add_pnl_stats(self, period_stats): + """ + Save p&l stats. + + Parameters + ---------- + period_stats + + Returns + ------- + + """ starting = period_stats['starting_cash'] current = period_stats['portfolio_value'] appreciation = (current / starting) - 1 @@ -466,6 +499,17 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): save_algo_df(self.algo_namespace, 'pnl_stats', self.pnl_stats) def add_custom_signals_stats(self, period_stats): + """ + Save custom signals stats. + + Parameters + ---------- + period_stats + + Returns + ------- + + """ log.debug('adding custom signals stats: {}'.format(self.recorded_vars)) df = pd.DataFrame( data=[self.recorded_vars], @@ -477,6 +521,17 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): self.custom_signals_stats) def add_exposure_stats(self, period_stats): + """ + Save exposure stats. + + Parameters + ---------- + period_stats + + Returns + ------- + + """ data = dict( long_exposure=period_stats['long_exposure'], base_currency=period_stats['ending_cash'] @@ -493,6 +548,14 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): self.exposure_stats) def handle_data(self, data): + """ + Wrapper around the handle_data method of each algo. + + Parameters + ---------- + data + + """ if not self.is_running: return @@ -619,15 +682,16 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): The cumulative portfolio does not contain open orders but exchange portfolios do. - :param asset: TradingPair - :param amount: float - :param limit_price: float - :param stop_price: float - :param style: Style - :return order: Order + Parameters + ---------- + asset: TradingPair + amount: float + limit_price: float + stop_price: float + style: Style + order: Order The catalyst order object or None """ - amount, style = self._calculate_order(asset, amount, limit_price, stop_price, style) @@ -689,15 +753,53 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase): 'get_open_orders. Use `asset` instead.') @api_method def get_open_orders(self, asset=None): + """Retrieve all of the current open orders. + + Parameters + ---------- + asset : Asset + If passed and not None, return only the open orders for the given + asset instead of all open orders. + + Returns + ------- + open_orders : dict[list[Order]] or list[Order] + If no asset is passed this will return a dict mapping Assets + to a list containing all the open orders for the asset. + If an asset is passed then this will return a list of the open + orders for this asset. + """ return self._get_open_orders(asset) @api_method def get_order(self, order_id, exchange_name): + """Lookup an order based on the order id returned from one of the + order functions. + + Parameters + ---------- + order_id : str + The unique identifier for the order. + + Returns + ------- + order : Order + The order object. + execution_price: float + The execution price per share of the order + """ exchange = self.exchanges[exchange_name] return exchange.get_order(order_id) @api_method def cancel_order(self, order_param, exchange_name): + """Cancel an open order. + + Parameters + ---------- + order_param : str or Order + The order_id or order object to cancel. + """ exchange = self.exchanges[exchange_name] order_id = order_param diff --git a/catalyst/exchange/exchange_bcolz.py b/catalyst/exchange/exchange_bcolz.py index 326745ac..10e85488 100644 --- a/catalyst/exchange/exchange_bcolz.py +++ b/catalyst/exchange/exchange_bcolz.py @@ -39,17 +39,25 @@ class BcolzExchangeBarReader(BcolzMinuteBarReader): return self._data_frequency def load_raw_arrays(self, fields, start_dt, end_dt, sids): + """ + Parameters + ---------- + fields : list of str + 'open', 'high', 'low', 'close', or 'volume' + start_dt: Timestamp + Beginning of the window range. + end_dt: Timestamp + End of the window range. + sids : list of int + The asset identifiers in the window. - # if self._data_frequency == 'minute': - # return super(BcolzExchangeBarReader, self) \ - # .load_raw_arrays(fields, start_dt, end_dt, sids) - # - # else: - # return self._load_daily_raw_arrays(fields, start_dt, end_dt, sids) - - return self._load_raw_arrays(fields, start_dt, end_dt, sids) - - def _load_raw_arrays(self, fields, start_dt, end_dt, sids): + Returns + ------- + list of np.ndarray + A list with an entry per field of ndarrays with shape + (minutes in range, sids) with a dtype of float64, containing the + values for the respective field over start and end dt range. + """ start_idx = self._find_position_of_minute(start_dt) end_idx = self._find_position_of_minute(end_dt) diff --git a/catalyst/exchange/exchange_bundle.py b/catalyst/exchange/exchange_bundle.py index ec499334..38de8e9e 100644 --- a/catalyst/exchange/exchange_bundle.py +++ b/catalyst/exchange/exchange_bundle.py @@ -59,7 +59,10 @@ class ExchangeBundle: """ Get a data writer object, either a new object or from cache - :return: BcolzMinuteBarReader or BcolzDailyBarReader + Returns + ------- + BcolzMinuteBarReader | BcolzDailyBarReader + """ if path is None: root = get_exchange_folder(self.exchange.name) @@ -88,7 +91,10 @@ class ExchangeBundle: """ Get a data writer object, either a new object or from cache - :return: BcolzMinuteBarWriter or BcolzDailyBarWriter + Returns + ------- + BcolzMinuteBarWriter | BcolzDailyBarWriter + """ root = get_exchange_folder(self.exchange.name) path = BUNDLE_NAME_TEMPLATE.format( @@ -144,13 +150,19 @@ class ExchangeBundle: If the data exists, the chunk ingestion is complete. If any data is missing we ingest the data. - :param assets: list[TradingPair] + Parameters + ---------- + assets: list[TradingPair] The assets is scope. - :param start_dt: + start_dt: datetime The chunk start date. - :param end_dt: + end_dt: datetime The chunk end date. - :return: list[TradingPair] + data_frequency: str + + Returns + ------- + list[TradingPair] The assets missing from the bundle """ reader = self.get_reader(data_frequency) @@ -164,13 +176,6 @@ class ExchangeBundle: return missing_assets def _write(self, data, writer, data_frequency): - """ - Write data to the writer - - :param df: - :param writer: - :return: - """ try: writer.write( data=data, @@ -195,6 +200,20 @@ class ExchangeBundle: ) def get_calendar_periods_range(self, start_dt, end_dt, data_frequency): + """ + Get a list of dates for the specified range. + + Parameters + ---------- + start_dt: datetime + end_dt: datetime + data_frequency: str + + Returns + ------- + list[datetime] + + """ return self.calendar.minutes_in_range(start_dt, end_dt) \ if data_frequency == 'minute' \ else self.calendar.sessions_in_range(start_dt, end_dt) @@ -204,13 +223,14 @@ class ExchangeBundle: """ Ingest a DataFrame of OHLCV data for a given market. - :param ohlcv_df: - :param data_frequency: - :param asset: - :param writer: - :param path: - :param empty_rows_behavior: - :return: + Parameters + ---------- + ohlcv_df: DataFrame + data_frequency: str + asset: TradingPair + writer: + empty_rows_behavior: str + """ if empty_rows_behavior is not 'ignore': nan_rows = ohlcv_df[ohlcv_df.isnull().T.any().T].index @@ -269,14 +289,16 @@ class ExchangeBundle: """ Merge a ctable bundle chunk into the main bundle for the exchange. - :param asset: TradingPair - :param data_frequency: str - :param period: str - :param writer: - :param empty_rows_behavior: str + Parameters + ---------- + asset: TradingPair + data_frequency: str + period: str + writer: + empty_rows_behavior: str Ensure that the bundle does not have any missing data. - :param cleanup: bool + cleanup: bool Remove the temp bundle directory after ingestion. :return: @@ -331,13 +353,19 @@ class ExchangeBundle: def get_adj_dates(self, start, end, assets, data_frequency): """ - Contains a date range to the trading availability of the specified pairs. + Contains a date range to the trading availability of the specified + markets. - :param start: - :param end: - :param assets: - :param data_frequency: - :return: + Parameters + ---------- + start: datetime + end: datetime + assets: list[TradingPair] + data_frequency: str + + Returns + ------- + datetime, datetime """ earliest_trade = None last_entry = None @@ -380,11 +408,17 @@ class ExchangeBundle: Split a price data request into chunks corresponding to individual bundles. - :param assets: - :param data_frequency: - :param start_dt: - :param end_dt: - :return: + Parameters + ---------- + assets: list[TradingPair] + data_frequency: str + start_dt: datetime + end_dt: datetime + + Returns + ------- + dict[TradingPair, list[dict(str, Object]]] + """ reader = self.get_reader(data_frequency) @@ -456,10 +490,12 @@ class ExchangeBundle: """ Determine if data is missing from the bundle and attempt to ingest it. - :param assets: - :param start_dt: - :param end_dt: - :return: + Parameters + ---------- + assets: list[TradingPair] + start_dt: datetime + end_dt: datetime + """ if start_dt is None: @@ -538,15 +574,18 @@ class ExchangeBundle: exclude_symbols=None, start=None, end=None, show_progress=True, environ=os.environ): """ + Inject data based on specified parameters. + + Parameters + ---------- + data_frequency: str + include_symbols: str + exclude_symbols: str + start: datetime + end: datetime + show_progress: bool + environ: - :param data_frequency: - :param include_symbols: - :param exclude_symbols: - :param start: - :param end: - :param show_progress: - :param environ: - :return: """ assets = self.get_assets(include_symbols, exclude_symbols) @@ -562,16 +601,22 @@ class ExchangeBundle: data_frequency, # type: str algo_end_dt=None # type: Timestamp ): - # type: (...) -> Dict[str, Series] """ Retrieve price data history, ingest missing data. - :param assets: - :param end_dt: - :param bar_count: - :param field: - :param data_frequency: - :return: + Parameters + ---------- + assets: list[TradingPair] + end_dt: datetime + bar_count: int + field: str + data_frequency: str + algo_end_dt: datetime + + Returns + ------- + Series + """ try: series = self.get_history_window_series( diff --git a/catalyst/exchange/exchange_data_portal.py b/catalyst/exchange/exchange_data_portal.py index f8d93288..6965fe24 100644 --- a/catalyst/exchange/exchange_data_portal.py +++ b/catalyst/exchange/exchange_data_portal.py @@ -1,16 +1,3 @@ -# -# 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. - import abc from time import sleep @@ -238,6 +225,25 @@ class DataPortalExchangeLive(DataPortalExchangeBase): field, data_frequency, ffill=True): + """ + Fetching price history window from the exchange. + + Parameters + ---------- + exchange: Exchange + assets: list[TradingPair] + end_dt: datetime + bar_count: int + frequency: str + field: str + data_frequency: str + ffill: bool + + Returns + ------- + DataFrame + + """ df = exchange.get_history_window( assets, end_dt, @@ -250,6 +256,22 @@ class DataPortalExchangeLive(DataPortalExchangeBase): def get_exchange_spot_value(self, exchange, assets, field, dt, data_frequency): + """ + A spot value for the exchange. + + Parameters + ---------- + exchange: Exchange + assets: list[TradingPair] + field: str + dt: datetime + data_frequency: str + + Returns + ------- + float + + """ exchange_spot_values = exchange.get_spot_value( assets, field, dt, data_frequency) @@ -288,18 +310,21 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase): """ Fetching price history window from the exchange bundle. - Using a try... except approach to minimize reads most of the time, - when the data exists. + Parameters + ---------- + exchange: Exchange + assets: list[TradingPair] + end_dt: datetime + bar_count: int + frequency: str + field: str + data_frequency: str + ffill: bool + + Returns + ------- + DataFrame - :param exchange: - :param assets: - :param end_dt: - :param bar_count: - :param frequency: - :param field: - :param data_frequency: - :param ffill: - :return: """ bundle = self.exchange_bundles[exchange.name] # type: ExchangeBundle @@ -321,26 +346,30 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase): return df def get_exchange_spot_value(self, - exchange, # type: Exchange - assets, # type: List[TradingPair] - field, # type: str - dt, # type: Timestamp - data_frequency # type: str + exchange, + assets, + field, + dt, + data_frequency ): - # type: (...) -> float """ A spot value for the exchange bundle. Try to ingest data if not in the bundle. - :param exchange: - :param assets: - :param field: - :param dt: - :param data_frequency: - :return: + Parameters + ---------- + exchange: Exchange + assets: list[TradingPair] + field: str + dt: datetime + data_frequency: str + + Returns + ------- + float + """ bundle = self.exchange_bundles[exchange.name] - if data_frequency == 'daily': dt = dt.floor('1D') else: diff --git a/catalyst/exchange/exchange_execution.py b/catalyst/exchange/exchange_execution.py index 6527678e..536b526a 100644 --- a/catalyst/exchange/exchange_execution.py +++ b/catalyst/exchange/exchange_execution.py @@ -4,9 +4,16 @@ from catalyst.finance.execution import LimitOrder, StopOrder, StopLimitOrder class ExchangeLimitOrder(LimitOrder): def get_limit_price(self, is_buy): """ - We may be trading Satoshis with 8 decimals, we cannot round numbers - :param is_buy: - :return: + We may be trading Satoshis with 8 decimals, we cannot round numbers. + + Parameters + ---------- + is_buy: bool + + Returns + ------- + float + """ return self.limit_price @@ -14,9 +21,16 @@ class ExchangeLimitOrder(LimitOrder): class ExchangeStopOrder(StopOrder): def get_stop_price(self, is_buy): """ - We may be trading Satoshis with 8 decimals, we cannot round numbers - :param is_buy: - :return: + We may be trading Satoshis with 8 decimals, we cannot round numbers. + + Parameters + ---------- + is_buy: bool + + Returns + ------- + float + """ return self.stop_price @@ -24,16 +38,30 @@ class ExchangeStopOrder(StopOrder): class ExchangeStopLimitOrder(StopLimitOrder): def get_limit_price(self, is_buy): """ - We may be trading Satoshis with 8 decimals, we cannot round numbers - :param is_buy: - :return: + We may be trading Satoshis with 8 decimals, we cannot round numbers. + + Parameters + ---------- + is_buy: bool + + Returns + ------- + float + """ return self.limit_price def get_stop_price(self, is_buy): """ - We may be trading Satoshis with 8 decimals, we cannot round numbers - :param is_buy: - :return: + We may be trading Satoshis with 8 decimals, we cannot round numbers. + + Parameters + ---------- + is_buy: bool + + Returns + ------- + float + """ return self.stop_price diff --git a/catalyst/exchange/exchange_portfolio.py b/catalyst/exchange/exchange_portfolio.py index 2c0b1ac2..2003654b 100644 --- a/catalyst/exchange/exchange_portfolio.py +++ b/catalyst/exchange/exchange_portfolio.py @@ -3,6 +3,7 @@ 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) @@ -29,10 +30,15 @@ class ExchangePortfolio(Portfolio): self.positions_value = 0.0 self.open_orders = dict() - def calculate_pnl(self): - log.debug('calculating pnl') - def create_order(self, order): + """ + Create an open order and store in memory. + + Parameters + ---------- + order: Order + + """ log.debug('creating order {}'.format(order.id)) self.open_orders[order.id] = order @@ -47,6 +53,18 @@ class ExchangePortfolio(Portfolio): log.debug('open order added to portfolio') def execute_order(self, order, transaction): + """ + Update the open orders and positions to apply an executed order. + + Unlike with backtesting, we do not need to add slippage and fees. + The executed price includes transaction fees. + + Parameters + ---------- + order: Order + transaction: Transaction + + """ log.debug('executing order {}'.format(order.id)) del self.open_orders[order.id] @@ -71,7 +89,9 @@ 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] \ @@ -96,6 +116,14 @@ class ExchangePortfolio(Portfolio): log.debug('updated portfolio with executed order') def remove_order(self, order): + """ + Removing an open order. + + Parameters + ---------- + order: Order + + """ log.info('removing cancelled order {}'.format(order.id)) del self.open_orders[order.id] diff --git a/catalyst/exchange/exchange_utils.py b/catalyst/exchange/exchange_utils.py index 812c7776..ee242ab2 100644 --- a/catalyst/exchange/exchange_utils.py +++ b/catalyst/exchange/exchange_utils.py @@ -2,12 +2,11 @@ import json import os import pickle import re - -from catalyst.assets._assets import TradingPair -from six.moves.urllib import request from datetime import date, datetime import pandas as pd +from catalyst.assets._assets import TradingPair +from six.moves.urllib import request from catalyst.exchange.exchange_errors import ExchangeSymbolsNotFound, \ InvalidHistoryFrequencyError, InvalidHistoryFrequencyAlias @@ -22,9 +21,15 @@ def get_exchange_folder(exchange_name, environ=None): """ The root path of an exchange folder. - :param exchange_name: - :param environ: - :return: + Parameters + ---------- + exchange_name: str + environ: + + Returns + ------- + str + """ if not environ: environ = os.environ @@ -40,9 +45,15 @@ def get_exchange_symbols_filename(exchange_name, environ=None): """ The absolute path of the exchange's symbol.json file. - :param exchange_name: - :param environ: - :return: + Parameters + ---------- + exchange_name: + environ: + + Returns + ------- + str + """ exchange_folder = get_exchange_folder(exchange_name, environ) return os.path.join(exchange_folder, 'symbols.json') From a9a422c89276694cef355f877f16c130ddd6b887 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Wed, 1 Nov 2017 23:10:31 -0400 Subject: [PATCH 22/24] DOC: updating the code docstrings --- catalyst/exchange/bundle_utils.py | 202 ++++++++++++++++++-------- catalyst/exchange/exchange.py | 23 ++- catalyst/exchange/exchange_utils.py | 193 ++++++++++++++++++------ catalyst/exchange/live_graph_clock.py | 46 +++--- catalyst/exchange/stats_utils.py | 51 +++++-- 5 files changed, 374 insertions(+), 141 deletions(-) diff --git a/catalyst/exchange/bundle_utils.py b/catalyst/exchange/bundle_utils.py index 9d357e23..4510bce4 100644 --- a/catalyst/exchange/bundle_utils.py +++ b/catalyst/exchange/bundle_utils.py @@ -7,22 +7,42 @@ import numpy as np import pandas as pd import pytz -from catalyst.data.bundles import from_bundle_ingest_dirname from catalyst.data.bundles.core import download_without_progress -from catalyst.exchange.exchange_errors import NoDataAvailableOnExchange from catalyst.exchange.exchange_utils import get_exchange_bundles_folder -from catalyst.utils.deprecate import deprecated -from catalyst.utils.paths import data_path EXCHANGE_NAMES = ['bitfinex', 'bittrex', 'poloniex'] API_URL = 'http://data.enigma.co/api/v1' def get_date_from_ms(ms): + """ + The date from the number of miliseconds from the epoch. + + Parameters + ---------- + ms: int + + Returns + ------- + datetime + + """ return datetime.fromtimestamp(ms / 1000.0) def get_seconds_from_date(date): + """ + The number of seconds from the epoch. + + Parameters + ---------- + date: datetime + + Returns + ------- + int + + """ epoch = datetime.utcfromtimestamp(0) epoch = epoch.replace(tzinfo=pytz.UTC) @@ -33,16 +53,19 @@ def get_bcolz_chunk(exchange_name, symbol, data_frequency, period): """ Download and extract a bcolz bundle. - :param exchange_name: - :param symbol: - :param data_frequency: - :param period: - :return: + Parameters + ---------- + exchange_name: str + symbol: str + data_frequency: str + period: str - Note: + Returns + ------- + str Filename: bitfinex-daily-neo_eth-2017-10.tar.gz - """ + """ root = get_exchange_bundles_folder(exchange_name) name = '{exchange}-{frequency}-{symbol}-{period}'.format( exchange=exchange_name, @@ -67,11 +90,38 @@ def get_bcolz_chunk(exchange_name, symbol, data_frequency, period): def get_delta(periods, data_frequency): + """ + Get a time delta based on the specified data frequency. + + Parameters + ---------- + periods: int + data_frequency: str + + Returns + ------- + timedelta + + """ return timedelta(minutes=periods) \ if data_frequency == 'minute' else timedelta(days=periods) def get_periods_range(start_dt, end_dt, freq): + """ + Get a date range for the specified parameters. + + Parameters + ---------- + start_dt: datetime + end_dt: datetime + freq: str + + Returns + ------- + DateTimeIndex + + """ if freq == 'minute': freq = 'T' @@ -82,10 +132,38 @@ def get_periods_range(start_dt, end_dt, freq): def get_periods(start_dt, end_dt, freq): + """ + The number of periods in the specified range. + + Parameters + ---------- + start_dt: datetime + end_dt: datetime + freq: str + + Returns + ------- + int + + """ return len(get_periods_range(start_dt, end_dt, freq)) def get_start_dt(end_dt, bar_count, data_frequency): + """ + The start date based on specified end date and data frequency. + + Parameters + ---------- + end_dt: datetime + bar_count: int + data_frequency: str + + Returns + ------- + datetime + + """ periods = bar_count if periods > 1: delta = get_delta(periods, data_frequency) @@ -100,9 +178,15 @@ def get_period_label(dt, data_frequency): """ The period label for the specified date and frequency. - :param dt: - :param data_frequency: - :return: + Parameters + ---------- + dt: datetime + data_frequency: str + + Returns + ------- + str + """ return '{}-{:02d}'.format(dt.year, dt.month) if data_frequency == 'minute' \ else '{}'.format(dt.year) @@ -112,10 +196,16 @@ def get_month_start_end(dt, first_day=None, last_day=None): """ The first and last day of the month for the specified date. - :param dt: - :param first_day - :param last_day - :return: + Parameters + ---------- + dt: datetime + first_day: datetime + last_day: datetime + + Returns + ------- + datetime, datetime + """ month_range = calendar.monthrange(dt.year, dt.month) @@ -140,10 +230,17 @@ def get_year_start_end(dt, first_day=None, last_day=None): """ The first and last day of the year for the specified date. - :param dt: - :param first_day - :param last_day - :return: + Parameters + ---------- + + dt: datetime + first_day: datetime + last_day: datetime + + Returns + ------- + datetime, datetime + """ year_start = first_day if first_day \ else pd.to_datetime(date(dt.year, 1, 1), utc=True) @@ -154,6 +251,19 @@ def get_year_start_end(dt, first_day=None, last_day=None): def get_df_from_arrays(arrays, periods): + """ + A DataFrame from the specified OHCLV arrays. + + Parameters + ---------- + arrays: Object + periods: DateTimeIndex + + Returns + ------- + DataFrame + + """ ohlcv = dict() for index, field in enumerate( ['open', 'high', 'low', 'close', 'volume']): @@ -171,11 +281,17 @@ def range_in_bundle(asset, start_dt, end_dt, reader): Evaluate whether price data of an asset is included has been ingested in the exchange bundle for the given date range. - :param asset: - :param start_dt: - :param end_dt: - :param reader: - :return: + Parameters + ---------- + asset: TradingPair + start_dt: datetime + end_dt: datetime + reader: BcolzBarMinuteReader + + Returns + ------- + bool + """ has_data = True if has_data and reader is not None: @@ -199,35 +315,3 @@ def range_in_bundle(asset, start_dt, end_dt, reader): has_data = False return has_data - - -@deprecated -def find_most_recent_time(bundle_name): - """ - Find most recent "time folder" for a given bundle. - - :param bundle_name: - The name of the targeted bundle. - - :return folder: - The name of the time folder. - """ - try: - bundle_folders = os.listdir( - data_path([bundle_name]), - ) - except OSError: - return None - - most_recent_bundle = dict() - for folder in bundle_folders: - date = from_bundle_ingest_dirname(folder) - if not most_recent_bundle or date > \ - most_recent_bundle[list(most_recent_bundle.keys())[0]]: - most_recent_bundle = dict() - most_recent_bundle[folder] = date - - if most_recent_bundle: - return list(most_recent_bundle.keys())[0] - else: - return None diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index b1ad3fb9..f10bbaf0 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -397,17 +397,24 @@ class Exchange: """ Similar to 'get_spot_value' but for a single asset - Note - ---- + Notes + ----- We're writing each minute bar to disk using zipline's machinery. This is especially useful when running multiple algorithms concurrently. By using local data when possible, we try to reaching request limits on exchanges. - :param asset: - :param field: - :param data_frequency: - :return value: The spot value of the given asset / field + Parameters + ---------- + asset: TradingPair + field: str + data_frequency: str + + Returns + ------- + float + The spot value of the given asset / field + """ log.debug( 'fetching spot value {field} for symbol {symbol}'.format( @@ -503,7 +510,9 @@ class Exchange: Returns ------- - A dataframe containing the requested data. + DataFrame + A dataframe containing the requested data. + """ start_dt = get_start_dt(end_dt, bar_count, data_frequency) diff --git a/catalyst/exchange/exchange_utils.py b/catalyst/exchange/exchange_utils.py index ee242ab2..4db41d9e 100644 --- a/catalyst/exchange/exchange_utils.py +++ b/catalyst/exchange/exchange_utils.py @@ -63,9 +63,15 @@ def download_exchange_symbols(exchange_name, environ=None): """ Downloads the exchange's symbols.json from the repository. - :param exchange_name: - :param environ: - :return: response + Parameters + ---------- + exchange_name: str + environ: + + Returns + ------- + str + """ filename = get_exchange_symbols_filename(exchange_name) url = SYMBOLS_URL.format(exchange=exchange_name) @@ -77,9 +83,15 @@ def get_exchange_symbols(exchange_name, environ=None): """ The de-serialized content of the exchange's symbols.json. - :param exchange_name: - :param environ: - :return: + Parameters + ---------- + exchange_name: str + environ: + + Returns + ------- + Object + """ filename = get_exchange_symbols_filename(exchange_name) @@ -104,8 +116,14 @@ def get_symbols_string(assets): """ A concatenated string of symbols from a list of assets. - :param assets: - :return: + Parameters + ---------- + assets: list[TradingPair] + + Returns + ------- + str + """ array = [assets] if isinstance(assets, TradingPair) else assets return ', '.join([asset.symbol for asset in array]) @@ -115,9 +133,15 @@ def get_exchange_auth(exchange_name, environ=None): """ The de-serialized contend of the exchange's auth.json file. - :param exchange_name: - :param environ: - :return: + Parameters + ---------- + exchange_name: str + environ: + + Returns + ------- + Object + """ exchange_folder = get_exchange_folder(exchange_name, environ) filename = os.path.join(exchange_folder, 'auth.json') @@ -138,9 +162,15 @@ def get_algo_folder(algo_name, environ=None): """ The algorithm root folder of the algorithm. - :param algo_name: - :param environ: - :return: + Parameters + ---------- + algo_name: str + environ: + + Returns + ------- + str + """ if not environ: environ = os.environ @@ -156,11 +186,17 @@ def get_algo_object(algo_name, key, environ=None, rel_path=None): """ The de-serialized object of the algo name and key. - :param algo_name: - :param key: - :param environ: - :param rel_path: - :return: + Parameters + ---------- + algo_name: str + key: str + environ: + rel_path: str + + Returns + ------- + Object + """ if algo_name is None: return None @@ -186,12 +222,14 @@ def save_algo_object(algo_name, key, obj, environ=None, rel_path=None): """ Serialize and save an object by algo name and key. - :param algo_name: - :param key: - :param obj: - :param environ: - :param rel_path: - :return: + Parameters + ---------- + algo_name: str + key: str + obj: Object + environ: + rel_path: str + """ folder = get_algo_folder(algo_name, environ) @@ -209,11 +247,17 @@ def get_algo_df(algo_name, key, environ=None, rel_path=None): """ The de-serialized DataFrame of an algo name and key. - :param algo_name: - :param key: - :param environ: - :param rel_path: - :return: + Parameters + ---------- + algo_name: str + key: str + environ: + rel_path: str + + Returns + ------- + DataFrame + """ folder = get_algo_folder(algo_name, environ) @@ -236,12 +280,14 @@ def save_algo_df(algo_name, key, df, environ=None, rel_path=None): """ Serialize to csv and save a DataFrame by algo name and key. - :param algo_name: - :param key: - :param df: - :param environ: - :param rel_path: - :return: + Parameters + ---------- + algo_name: str + key: str + df: DataFrame + environ: + rel_path: str + """ folder = get_algo_folder(algo_name, environ) @@ -259,9 +305,15 @@ def get_exchange_minute_writer_root(exchange_name, environ=None): """ The minute writer folder for the exchange. - :param exchange_name: - :param environ: - :return: + Parameters + ---------- + exchange_name: str + environ: + + Returns + ------- + BcolzExchangeBarWriter + """ exchange_folder = get_exchange_folder(exchange_name, environ) @@ -275,9 +327,15 @@ def get_exchange_bundles_folder(exchange_name, environ=None): """ The temp folder for bundle downloads by algo name. - :param exchange_name: - :param environ: - :return: + Parameters + ---------- + exchange_name: str + environ: + + Returns + ------- + str + """ exchange_folder = get_exchange_folder(exchange_name, environ) @@ -291,8 +349,14 @@ def perf_serial(obj): """ JSON serializer for objects not serializable by default json code - :param obj: - :return: + Parameters + ---------- + obj: Object + + Returns + ------- + str + """ if isinstance(obj, (datetime, date)): return obj.isoformat() @@ -304,8 +368,14 @@ def get_common_assets(exchanges): """ The assets available in all specified exchanges. - :param exchanges: - :return: + Parameters + ---------- + exchanges: list[Exchange] + + Returns + ------- + list[TradingPair] + """ symbols = [] for exchange_name in exchanges: @@ -324,6 +394,23 @@ def get_common_assets(exchanges): def get_frequency(freq, data_frequency): + """ + Get the frequency parameters. + + Notes + ----- + We're trying to use Pandas convention for frequency aliases. + + Parameters + ---------- + freq: str + data_frequency: str + + Returns + ------- + str, int, str, str + + """ if freq == 'minute': unit = 'T' candle_size = 1 @@ -368,6 +455,20 @@ def get_frequency(freq, data_frequency): def resample_history_df(df, freq, field): + """ + Resample the OHCLV DataFrame using the specified frequency. + + Parameters + ---------- + df: DataFrame + freq: str + field: str + + Returns + ------- + DataFrame + + """ if field == 'open': agg = 'first' elif field == 'high': diff --git a/catalyst/exchange/live_graph_clock.py b/catalyst/exchange/live_graph_clock.py index ecc83677..6f674455 100644 --- a/catalyst/exchange/live_graph_clock.py +++ b/catalyst/exchange/live_graph_clock.py @@ -1,16 +1,3 @@ -# -# 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. - import pandas as pd from catalyst.gens.sim_engine import ( BAR, @@ -33,8 +20,8 @@ class LiveGraphClock(object): This mixes the clock with a live graph. - Note - ---- + Notes + ----- This seemingly awkward approach allows us to run the program using a single thread. This is important because Matplotlib does not play nice with multi-threaded environments. Zipline probably does not either. @@ -53,7 +40,7 @@ class LiveGraphClock(object): def __init__(self, sessions, context, time_skew=pd.Timedelta('0s')): - global mdates, plt #TODO: Could be cleaner + global mdates, plt # TODO: Could be cleaner import matplotlib.dates as mdates from matplotlib import pyplot as plt from matplotlib import style @@ -95,11 +82,12 @@ class LiveGraphClock(object): """ Trying to assign reasonable parameters to the time axis. - TODO: room for improvement + Parameters + ---------- + ax: - :param ax: - :return: """ + # TODO: room for improvement ax.xaxis.set_major_locator(mdates.DayLocator(interval=1)) ax.xaxis.set_major_formatter(self.fmt) @@ -113,9 +101,21 @@ class LiveGraphClock(object): ax.grid(True) def set_legend(self, ax): + """ + Set legend on the chart. + + Parameters + ---------- + ax + + """ ax.legend(loc='upper left', ncol=1, fontsize=10, numpoints=1) def draw_pnl(self): + """ + Draw p&l line on the chart. + + """ ax = self.ax_pnl df = self.context.pnl_stats @@ -136,6 +136,10 @@ class LiveGraphClock(object): self.format_ax(ax) def draw_custom_signals(self): + """ + Draw custom signals on the chart. + + """ ax = self.ax_custom_signals df = self.context.custom_signals_stats @@ -154,6 +158,10 @@ class LiveGraphClock(object): self.format_ax(ax) def draw_exposure(self): + """ + Draw exposure line on the chart. + + """ ax = self.ax_exposure context = self.context df = context.exposure_stats diff --git a/catalyst/exchange/stats_utils.py b/catalyst/exchange/stats_utils.py index bd968dfe..b7bfda98 100644 --- a/catalyst/exchange/stats_utils.py +++ b/catalyst/exchange/stats_utils.py @@ -1,5 +1,5 @@ -import pandas as pd import numpy as np +import pandas as pd def crossover(source, target): @@ -8,9 +8,15 @@ def crossover(source, target): of `x` is greater than the value of `y` and the value of `x` was less than the value of `y` on the bar immediately preceding the current bar. - :param source: - :param target: - :return: + Parameters + ---------- + source: Series + target: Series + + Returns + ------- + bool + """ if source[-1] is np.nan or source[-2] is np.nan \ or target[-1] is np.nan or target[-2] is np.nan: @@ -27,9 +33,16 @@ def crossunder(source, target): The `x`-series is defined as having crossed under `y`-series if the value of `x` is less than the value of `y` and the value of `x` was greater than the value of `y` on the bar immediately preceding the current bar. - :param source: - :param target: - :return: + + Parameters + ---------- + source: Series + target: Series + + Returns + ------- + bool + """ if source[-1] is np.nan or source[-2] is np.nan \ or target[-1] is np.nan or target[-2] is np.nan: @@ -46,9 +59,15 @@ 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. - :param stats_df: - :param num_rows: - :return: + 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) @@ -92,6 +111,18 @@ def get_pretty_stats(stats_df, recorded_cols=None, num_rows=10): def df_to_string(df): + """ + Create a formatted str representation of the DataFrame. + + Parameters + ---------- + df: DataFrame + + Returns + ------- + str + + """ pd.set_option('display.expand_frame_repr', False) pd.set_option('precision', 8) pd.set_option('display.width', 1000) From 5e4ad9b33842467d8017487ce41fdca1cf2cc3e3 Mon Sep 17 00:00:00 2001 From: fredfortier Date: Thu, 2 Nov 2017 20:18:34 -0400 Subject: [PATCH 23/24] BUG: accounting for daily historical bars with minute freq algo --- catalyst/examples/simple_loop.py | 24 +++-- catalyst/exchange/exchange_bundle.py | 20 ++-- catalyst/exchange/exchange_data_portal.py | 7 +- tests/exchange/test_bundle.py | 15 ++- tests/exchange/test_server_bundle.py | 124 ++++++++++++++++++++++ 5 files changed, 162 insertions(+), 28 deletions(-) create mode 100644 tests/exchange/test_server_bundle.py diff --git a/catalyst/examples/simple_loop.py b/catalyst/examples/simple_loop.py index 9f02e5ac..1deedf92 100644 --- a/catalyst/examples/simple_loop.py +++ b/catalyst/examples/simple_loop.py @@ -16,26 +16,28 @@ def handle_data(context, data): price = data.current(context.asset, 'close') print('got price {price}'.format(price=price)) - # prices = data.history( - # context.asset, - # fields='price', - # bar_count=20, - # frequency='1T' - # ) - # rsi = talib.RSI(prices.values, timeperiod=14)[-1] - # print('got rsi: {}'.format(rsi)) - pass + try: + prices = data.history( + context.asset, + fields='price', + bar_count=16, + frequency='1D' + ) + rsi = talib.RSI(prices.values, timeperiod=14)[-1] + print('got rsi: {}'.format(rsi)) + except Exception as e: + print(e) run_algorithm( capital_base=250, start=pd.to_datetime('2017-1-1', utc=True), end=pd.to_datetime('2017-10-22', utc=True), - data_frequency='daily', + data_frequency='minute', initialize=initialize, handle_data=handle_data, analyze=None, - exchange_name='poloniex', + exchange_name='bitfinex', algo_namespace='simple_loop', base_currency='btc' ) diff --git a/catalyst/exchange/exchange_bundle.py b/catalyst/exchange/exchange_bundle.py index 38de8e9e..f5df0f8e 100644 --- a/catalyst/exchange/exchange_bundle.py +++ b/catalyst/exchange/exchange_bundle.py @@ -284,7 +284,7 @@ class ExchangeBundle: self._write(data, writer, data_frequency) - def ingest_ctable(self, asset, data_frequency, period, start_dt, end_dt, + def ingest_ctable(self, asset, data_frequency, period, writer, empty_rows_behavior='strip', cleanup=False): """ Merge a ctable bundle chunk into the main bundle for the exchange. @@ -315,6 +315,12 @@ class ExchangeBundle: if reader is None: raise TempBundleNotFoundError(path=path) + start_dt = reader.first_trading_day + end_dt = reader.last_available_dt + + if data_frequency == 'daily': + end_dt = end_dt - pd.Timedelta(hours=23, minutes=59) + arrays = None try: arrays = reader.load_raw_arrays( @@ -420,6 +426,12 @@ class ExchangeBundle: dict[TradingPair, list[dict(str, Object]]] """ + get_start_end = get_month_start_end \ + if data_frequency == 'minute' else get_year_start_end + + start_dt, _ = get_start_end(start_dt) + _, end_dt = get_start_end(end_dt) + reader = self.get_reader(data_frequency) chunks = dict() @@ -450,8 +462,6 @@ class ExchangeBundle: chunks[asset] = [] for index, dt in enumerate(dates): - get_start_end = get_month_start_end \ - if data_frequency == 'minute' else get_year_start_end period_start, period_end = get_start_end( dt=dt, @@ -543,8 +553,6 @@ class ExchangeBundle: asset=chunk['asset'], data_frequency=data_frequency, period=chunk['period'], - start_dt=chunk['period_start'], - end_dt=chunk['period_end'], writer=writer, empty_rows_behavior='strip', cleanup=True @@ -563,8 +571,6 @@ class ExchangeBundle: asset=chunk['asset'], data_frequency=data_frequency, period=chunk['period'], - start_dt=chunk['period_start'], - end_dt=chunk['period_end'], writer=writer, empty_rows_behavior='strip', cleanup=True diff --git a/catalyst/exchange/exchange_data_portal.py b/catalyst/exchange/exchange_data_portal.py index 6965fe24..a6cf4db7 100644 --- a/catalyst/exchange/exchange_data_portal.py +++ b/catalyst/exchange/exchange_data_portal.py @@ -328,17 +328,20 @@ class DataPortalExchangeBacktest(DataPortalExchangeBase): """ bundle = self.exchange_bundles[exchange.name] # type: ExchangeBundle - freq, candle_size, unit, data_frequency = get_frequency( + freq, candle_size, unit, adj_data_frequency = get_frequency( frequency, data_frequency ) adj_bar_count = candle_size * bar_count + if data_frequency == 'minute' and adj_data_frequency == 'daily': + end_dt = end_dt.floor('1D') + series = bundle.get_history_window_series_and_load( assets=assets, end_dt=end_dt, bar_count=adj_bar_count, field=field, - data_frequency=data_frequency, + data_frequency=adj_data_frequency, algo_end_dt=self._last_available_session, ) diff --git a/tests/exchange/test_bundle.py b/tests/exchange/test_bundle.py index 2180dd03..c5e5feb9 100644 --- a/tests/exchange/test_bundle.py +++ b/tests/exchange/test_bundle.py @@ -431,7 +431,7 @@ class TestExchangeBundle: pass def bundle_to_csv(self): - exchange_name = 'poloniex' + exchange_name = 'bitfinex' data_frequency = 'daily' period = '2016' @@ -445,14 +445,13 @@ class TestExchangeBundle: data_frequency=data_frequency, period=period ) - - dt = pd.to_datetime(period, utc=True) - if data_frequency == 'minute': - start_dt, end_dt = get_month_start_end(dt) - else: - start_dt, end_dt = get_year_start_end(dt) - reader = bundle.get_reader(data_frequency, path=path) + start_dt = reader.first_trading_day + end_dt = reader.last_available_dt + + if data_frequency == 'daily': + end_dt = end_dt - pd.Timedelta(hours=23, minutes=59) + arrays = None try: arrays = reader.load_raw_arrays( diff --git a/tests/exchange/test_server_bundle.py b/tests/exchange/test_server_bundle.py new file mode 100644 index 00000000..50dd98d7 --- /dev/null +++ b/tests/exchange/test_server_bundle.py @@ -0,0 +1,124 @@ +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 +import matplotlib.ticker as ticker + +from catalyst.exchange.factory import get_exchange + +EXCHANGE_NAMES = ['bitfinex', 'bittrex', 'poloniex'] +exchanges = dict((e, getattr(importlib.import_module( + 'catalyst.exchange.{0}.{0}'.format(e)), e.capitalize())) + for e in EXCHANGE_NAMES) + + +class ValidateChunks(object): + def __init__(self): + self.columns = ['open', 'high', 'low', 'close', 'volume'] + + def chunk_to_df(self, exchange_name, symbol, data_frequency, period): + + exchange = get_exchange(exchange_name) + asset = exchange.get_asset(symbol) + + filename = get_bcolz_chunk( + exchange_name=exchange_name, + symbol=symbol, + data_frequency=data_frequency, + period=period + ) + + reader = BcolzExchangeBarReader(rootdir=filename, + data_frequency=data_frequency) + + # metadata = BcolzMinuteBarMetadata.read(filename) + + start = reader.first_trading_day + end = reader.last_available_dt + + if data_frequency == 'daily': + end = end - pd.Timedelta(hours=23, minutes=59) + + print start, end, data_frequency + + arrays = reader.load_raw_arrays(self.columns, start, end, + [asset.sid, ]) + + bundle = ExchangeBundle(exchange_name) + + periods = bundle.get_calendar_periods_range( + start, end, data_frequency + ) + + return get_df_from_arrays(arrays, periods) + + def plot_ohlcv(self, df): + + fig, ax = plt.subplots() + + # Plot the candlestick + candlestick2_ohlc(ax, df['open'], df['high'], df['low'], df['close'], + width=1, colorup='g', colordown='r', alpha=0.5) + + # shift y-limits of the candlestick plot so that there is space + # at the bottom for the volume bar chart + pad = 0.25 + yl = ax.get_ylim() + ax.set_ylim(yl[0] - (yl[1] - yl[0]) * pad, yl[1]) + + # Add a seconds axis for the volume overlay + ax2 = ax.twinx() + + ax2.set_position( + 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) + + ax.xaxis.set_major_locator(ticker.MaxNLocator(6)) + + def mydate(x, pos): + try: + return df.index[int(x)] + except IndexError: + return '' + + ax.xaxis.set_major_formatter(ticker.FuncFormatter(mydate)) + plt.margins(0) + plt.show() + + def plot(self, filename): + df = self.chunk_to_df(filename) + self.plot_ohlcv(df) + + def to_csv(self, filename): + df = self.chunk_to_df(filename) + df.to_csv(os.path.basename(filename).split('.')[0] + '.csv') + + +v = ValidateChunks() + +df = v.chunk_to_df( + exchange_name='bitfinex', + symbol='eth_btc', + data_frequency='daily', + period='2016' +) +print(df.tail()) +v.plot_ohlcv(df) +# v.plot( +# ex +# ) From 2bbc0c00ccd9281914c33082e9549e7453e08e0a Mon Sep 17 00:00:00 2001 From: fredfortier Date: Thu, 2 Nov 2017 20:44:16 -0400 Subject: [PATCH 24/24] BLD: updating test algo --- catalyst/examples/simple_loop.py | 34 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/catalyst/examples/simple_loop.py b/catalyst/examples/simple_loop.py index 1deedf92..9ebf93b1 100644 --- a/catalyst/examples/simple_loop.py +++ b/catalyst/examples/simple_loop.py @@ -21,7 +21,7 @@ def handle_data(context, data): context.asset, fields='price', bar_count=16, - frequency='1D' + frequency='5T' ) rsi = talib.RSI(prices.values, timeperiod=14)[-1] print('got rsi: {}'.format(rsi)) @@ -29,25 +29,25 @@ def handle_data(context, data): print(e) -run_algorithm( - capital_base=250, - 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=handle_data, - analyze=None, - exchange_name='bitfinex', - algo_namespace='simple_loop', - base_currency='btc' -) # run_algorithm( +# capital_base=250, +# start=pd.to_datetime('2017-1-1', utc=True), +# end=pd.to_datetime('2017-10-22', utc=True), +# data_frequency='daily', # initialize=initialize, # handle_data=handle_data, # analyze=None, -# exchange_name='bittrex', -# live=True, +# exchange_name='bitfinex', # algo_namespace='simple_loop', -# base_currency='eth', -# live_graph=False +# base_currency='btc' # ) +run_algorithm( + initialize=initialize, + handle_data=handle_data, + analyze=None, + exchange_name='poloniex', + live=True, + algo_namespace='simple_loop', + base_currency='eth', + live_graph=False +)