From 9cfd50dc4fbb14f3afef392f3ebd9649b9873595 Mon Sep 17 00:00:00 2001 From: Victor Grau Serrat Date: Mon, 20 Nov 2017 09:12:43 -0700 Subject: [PATCH] DOC: mean_reversion_simple.py minor edits, and added to doc website --- catalyst/examples/mean_reversion_simple.py | 13 +- docs/source/example-algos.rst | 276 ++++++++++++++++++++- 2 files changed, 280 insertions(+), 9 deletions(-) diff --git a/catalyst/examples/mean_reversion_simple.py b/catalyst/examples/mean_reversion_simple.py index 9aa5ada3..d5efbea8 100644 --- a/catalyst/examples/mean_reversion_simple.py +++ b/catalyst/examples/mean_reversion_simple.py @@ -4,22 +4,21 @@ import pandas as pd import talib -# To run an algorithm in Catalyst, you need two functions: initialize and -# handle_data. from logbook import Logger from catalyst import run_algorithm -from catalyst.api import symbol, record, order_target_percent, \ - get_open_orders +from catalyst.api import symbol, record, order_target_percent, get_open_orders +from catalyst.exchange.stats_utils import extract_transactions + # We give a name to the algorithm which Catalyst will use to persist its state. # In this example, Catalyst will create the `.catalyst/data/live_algos` # directory. If we stop and start the algorithm, Catalyst will resume its # state using the files included in the folder. -from catalyst.exchange.stats_utils import extract_transactions - NAMESPACE = 'mean_reversion_simple' log = Logger(NAMESPACE) +# To run an algorithm in Catalyst, you need two functions: initialize and +# handle_data. def initialize(context): # This initialize function sets any data or variables that you'll use in @@ -216,7 +215,7 @@ def analyze(context=None, perf=None): if __name__ == '__main__': # The execution mode: backtest or live - MODE = 'live' + MODE = 'backtest' if MODE == 'backtest': # catalyst run -f catalyst/examples/mean_reversion_simple.py -x poloniex -s 2017-10-1 -e 2017-11-10 -c usdt -n mean-reversion --data-frequency minute --capital-base 10000 diff --git a/docs/source/example-algos.rst b/docs/source/example-algos.rst index 7882208d..e341f331 100644 --- a/docs/source/example-algos.rst +++ b/docs/source/example-algos.rst @@ -6,8 +6,10 @@ This section documents a small number of example algorithms to complement the beginner tutorial, and show how other trading algorithms can be implemented using Catalyst: -Buy and Hodl -~~~~~~~~~~~~ +.. _buy_and_hodl: + +Buy and Hodl Algorithm +~~~~~~~~~~~~~~~~~~~~~~ source: `examples/buy_and_hodl.py `_ @@ -170,3 +172,273 @@ one day prior to the current date. plt.gcf().set_size_inches(18, 8) plt.show() +.. _mean_reversion: + +Mean Reversion Algorithm +~~~~~~~~~~~~~~~~~~~~~~~~ + +source: `examples/mean_reversion_simple.py `_ + +This algorithm is based on a simple momentum strategy. When the cryptoasset goes +up quickly, we're going to buy; when it goes down quickly, we're going to sell. +Hopefully, we'll ride the waves. + +We are choosing to run this trading algorithm with the ``neo_usd`` currency pair +on the ``Bitfinex`` exchange. Thus, first ingest the historical pricing data +that we need, with minute resolution: + +.. code-block:: bash + + catalyst ingest-exchange -x bitfinex -f minute -i neo_usd + +To run this algorithm, we are opting for the Python interpreter, instead of the +command line (CLI). All of the parameters for the simulation are specified in +lines 218-245, so in order to run the algorithm we just type: + +.. code-block:: bash + + python mean_reversion_simple.py + +.. code-block:: python + + import pandas as pd + import talib + from logbook import Logger + + from catalyst import run_algorithm + from catalyst.api import symbol, record, order_target_percent, get_open_orders + from catalyst.exchange.stats_utils import extract_transactions + + # We give a name to the algorithm which Catalyst will use to persist its state. + # In this example, Catalyst will create the `.catalyst/data/live_algos` + # directory. If we stop and start the algorithm, Catalyst will resume its + # state using the files included in the folder. + NAMESPACE = 'mean_reversion_simple' + log = Logger(NAMESPACE) + + # To run an algorithm in Catalyst, you need two functions: initialize and + # handle_data. + + def initialize(context): + # This initialize function sets any data or variables that you'll use in + # your algorithm. For instance, you'll want to define the trading pair (or + # trading pairs) you want to backtest. You'll also want to define any + # parameters or values you're going to use. + + # In our example, we're looking at Ether in USD Tether. + context.neo_usd = symbol('neo_usd') + context.base_price = None + context.current_day = None + + + def handle_data(context, data): + # This handle_data function is where the real work is done. Our data is + # minute-level tick data, and each minute is called a frame. This function + # runs on each frame of the data. + + # We flag the first period of each day. + # Since cryptocurrencies trade 24/7 the `before_trading_starts` handle + # would only execute once. This method works with minute and daily + # frequencies. + today = data.current_dt.floor('1D') + if today != context.current_day: + context.traded_today = False + context.current_day = today + + # We're computing the volume-weighted-average-price of the security + # defined above, in the context.neo_usd variable. For this example, we're + # using three bars on the 15 min bars. + + # The frequency attribute determine the bar size. We use this convention + # for the frequency alias: + # http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases + prices = data.history( + context.neo_usd, + fields='close', + bar_count=50, + frequency='15T' + ) + + # Ta-lib calculates various technical indicator based on price and + # volume arrays. + + # In this example, we are comp + rsi = talib.RSI(prices.values, timeperiod=14) + + # We need a variable for the current price of the security to compare to + # the average. Since we are requesting two fields, data.current() + # returns a DataFrame with + current = data.current(context.neo_usd, fields=['close', 'volume']) + price = current['close'] + + # If base_price is not set, we use the current value. This is the + # price at the first bar which we reference to calculate price_change. + if context.base_price is None: + context.base_price = price + + price_change = (price - context.base_price) / context.base_price + cash = context.portfolio.cash + + # Now that we've collected all current data for this frame, we use + # the record() method to save it. This data will be available as + # a parameter of the analyze() function for further analysis. + record( + price=price, + volume=current['volume'], + price_change=price_change, + rsi=rsi[-1], + cash=cash + ) + + # We are trying to avoid over-trading by limiting our trades to + # one per day. + if context.traded_today: + return + + # Since we are using limit orders, some orders may not execute immediately + # we wait until all orders are executed before considering more trades. + orders = get_open_orders(context.neo_usd) + if len(orders) > 0: + return + + # Exit if we cannot trade + if not data.can_trade(context.neo_usd): + return + + # Another powerful built-in feature of the Catalyst backtester is the + # portfolio object. The portfolio object tracks your positions, cash, + # cost basis of specific holdings, and more. In this line, we calculate + # how long or short our position is at this minute. + pos_amount = context.portfolio.positions[context.neo_usd].amount + + if rsi[-1] <= 30 and pos_amount == 0: + log.info( + '{}: buying - price: {}, rsi: {}'.format( + data.current_dt, price, rsi[-1] + ) + ) + order_target_percent(context.neo_usd, 1) + context.traded_today = True + + elif rsi[-1] >= 80 and pos_amount > 0: + log.info( + '{}: selling - price: {}, rsi: {}'.format( + data.current_dt, price, rsi[-1] + ) + ) + order_target_percent(context.neo_usd, 0) + context.traded_today = True + + + def analyze(context=None, perf=None): + import matplotlib.pyplot as plt + + # The base currency of the algo exchange + base_currency = context.exchanges.values()[0].base_currency.upper() + + # Plot the portfolio value over time. + ax1 = plt.subplot(611) + perf.loc[:, 'portfolio_value'].plot(ax=ax1) + ax1.set_ylabel('Portfolio Value ({})'.format(base_currency)) + + # Plot the price increase or decrease over time. + ax2 = plt.subplot(612, sharex=ax1) + perf.loc[:, 'price'].plot(ax=ax2, label='Price') + + ax2.set_ylabel('{asset} ({base})'.format( + asset=context.neo_usd.symbol, base=base_currency + )) + + transaction_df = extract_transactions(perf) + if not transaction_df.empty: + buy_df = transaction_df[transaction_df['amount'] > 0] + sell_df = transaction_df[transaction_df['amount'] < 0] + ax2.scatter( + buy_df.index.to_pydatetime(), + perf.loc[buy_df.index, 'price'], + marker='^', + s=100, + c='green', + label='' + ) + ax2.scatter( + sell_df.index.to_pydatetime(), + perf.loc[sell_df.index, 'price'], + marker='v', + s=100, + c='red', + label='' + ) + + ax4 = plt.subplot(613, sharex=ax1) + perf.loc[:, 'cash'].plot( + ax=ax4, label='Base Currency ({})'.format(base_currency) + ) + ax4.set_ylabel('Cash ({})'.format(base_currency)) + + perf['algorithm'] = perf.loc[:, 'algorithm_period_return'] + + ax5 = plt.subplot(614, sharex=ax1) + perf.loc[:, ['algorithm', 'price_change']].plot(ax=ax5) + ax5.set_ylabel('Percent Change') + + ax6 = plt.subplot(615, sharex=ax1) + perf.loc[:, 'rsi'].plot(ax=ax6, label='RSI') + ax6.axhline(70, color='darkgoldenrod') + ax6.axhline(30, color='darkgoldenrod') + + if not transaction_df.empty: + ax6.scatter( + buy_df.index.to_pydatetime(), + perf.loc[buy_df.index, 'rsi'], + marker='^', + s=100, + c='green', + label='' + ) + ax6.scatter( + sell_df.index.to_pydatetime(), + perf.loc[sell_df.index, 'rsi'], + marker='v', + s=100, + c='red', + label='' + ) + plt.legend(loc=3) + + # Show the plot. + plt.gcf().set_size_inches(18, 8) + plt.show() + pass + + + if __name__ == '__main__': + # The execution mode: backtest or live + MODE = 'backtest' + + if MODE == 'backtest': + # catalyst run -f catalyst/examples/mean_reversion_simple.py -x poloniex -s 2017-10-1 -e 2017-11-10 -c usdt -n mean-reversion --data-frequency minute --capital-base 10000 + run_algorithm( + capital_base=10000, + data_frequency='minute', + initialize=initialize, + handle_data=handle_data, + analyze=analyze, + exchange_name='bitfinex', + algo_namespace=NAMESPACE, + base_currency='usd', + start=pd.to_datetime('2017-10-1', utc=True), + end=pd.to_datetime('2017-11-10', utc=True), + ) + + elif MODE == 'live': + run_algorithm( + initialize=initialize, + handle_data=handle_data, + analyze=analyze, + exchange_name='bitfinex', + live=True, + algo_namespace=NAMESPACE, + base_currency='usd', + live_graph=True + )