Crypto Algo Trading with Catalyst in Under 20 Minutes
Over the past few years, there has been a meteoric rise in the number of algorithmic trading strategies, and in their effectiveness. Reports have demonstrated that there have been dramatic increases in quant-driven trading volume and investments into quant funds, as well as outsized returns for algorithmic funds vs. traditional ones over the past five years. Algorithmic trading empowers developers, mathematicians, and traders to express their trading ideas and philosophies in a scientific manner, without succumbing to the dangers of emotions, careless mistakes, and latency.
If it sounds intimidating, don’t worry — Catalyst allows you (yes, you!) to easily professionalize your crypto trading and get started building your own trading strategies!
What is Catalyst?
Catalyst is a powerful algorithmic investing platform and infrastructure to trade crypto-assets. It allows users to focus on developing and implementing novel strategies, without having to concern themselves over the mechanics of data warehousing/streaming, exchange-specific order entry code, and other tedious obstacles that would otherwise stand in the way. Users can quickly write and simulate algorithms across years of daily and minute-resolution historical data, analyze performance, and switch to live trading mode on a number of leading exchanges.
In this post, I will demonstrate how you can get started with Catalyst in under 20 minutes. Whether you are a programming or trading beginner or expert, this walk-through guide will help kickstart your journey into the ripe algorithmic crypto-trading ecosystem.
Note: As a Python-based tool belt for trading crypto-assets, the stronger the understanding and knowledge of Python and associated data-analysis packages (Pandas, etc.), the better! If you are unfamiliar with Python, I suggest you take a CodeAcademy course on Python basics and play around with Pandas data frames before proceeding. You’ll be thankful later!
Installing Python
My recommended Python installation process is through the Anaconda distribution, which can be found here. The data marketplace (to be discussed in a following post) is only compatible with Python 3 onwards, so I strongly recommend you download and install the Python 3.6 version as opposed to the Python 2.7 version. For those of you unfamiliar with Conda, it’s a wonderful package dependency and environment management system that is more robust than pip alone.
Not working perfectly? Click here to get help on the Catalyst forum | Click here to ask our team in Discord
Installing PyCharm
PyCharm is an excellent IDE for professional and casual Python developers alike, and can be downloaded for free here. It facilitates smart code completion, efficient refactoring, debugging, and Git integration, among other features.
Not working perfectly? Click here to get help on the Catalyst forum | Click here to ask our team in Discord
Installing Catalyst
Now that you have Python 3 and the Conda distribution system installed, it becomes very easy to set yourself up with the necessary packages required to use Catalyst. Conda supports the creation of environments, which are directories comprised of a set of packages need to run and execute programs as desired, isolated from root and custom environments (similar to virtualenvs for those of you more familiar with pip). Catalyst has provided a Conda environment for you located here, so be sure to save this locally. You can then create this environment, named catalyst:
conda env create -f /path/to/python3.6-environment.yml
From this point forward, you should work within this env by entering conda activate catalyst
(and can subsequently return to your root env with conda deactivate
if you’d like).
Be sure to point your PyCharm editor’s interpreter to this new environment. From the command line within the catalyst env, enter which python
and copy the result. From the PyCharm menu, navigate to Preferences > Project Interpreter, click the small gear on the top right, click Add Local, and paste the path you’ve copied here.
If you’re super familiar with pip (or I haven’t sold you on using Conda instead just yet), don’t worry, you can install Catalyst via pip by following the instructions laid out in the documentation.
You’re now done with all the boring stuff, so pat yourself on the back and let’s start writing strategies!
Not working perfectly? Click here to get help on the Catalyst forum | Click here to ask our team in Discord
Moving Average Crossover Momentum Strategy for BTC/USD (Bitfinex)
For this example, we will be trading the BTC/USD pair on Bitfinex, with a simple moving average (MA) crossover strategy. The parameters are customizable so feel free to play around, but for now, we use 20-day (fast) and 50-day (slow) MAs. The MA crossover trade is often viewed as the “Hello World” of algorithmic trading, but if you are unfamiliar with it, the intuition is that if the fast MA crosses above the slow MA, there is bullish momentum, and thus we long (buy) the asset. If the fast MA crosses below the slow MA, this is a bearish sign, and we exit our long position. Feel free to copy and paste the block of code below into your PyCharm editor.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from logbook import Logger
from catalyst import run_algorithm
from catalyst.api import (record, symbol, order_target_percent, )
from catalyst.exchange.utils.stats_utils import extract_transactions
NAMESPACE = 'dual_moving_average'
log = Logger(NAMESPACE)
def initialize(context):
context.i = 0
context.asset = symbol('btc_usd')
context.base_price = None
def handle_data(context, data):
# define the windows for the moving averages
short_window = 20
long_window = 50
# Skip as many bars as long_window to properly compute the average
context.i += 1
if context.i < long_window:
return
# Compute moving averages calling data.history() for each
# moving average with the appropriate parameters. We choose to use
# daily bars for this simulation -> freq="1m"
# Returns a pandas dataframe.
short_data = data.history(context.asset,
'price',
bar_count=short_window,
frequency="1D",
)
short_mavg = short_data.mean()
long_data = data.history(context.asset,
'price',
bar_count=long_window,
frequency="1D",
)
long_mavg = long_data.mean()
# Let's keep the price of our asset in a more handy variable
price = data.current(context.asset, 'price')
# 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
# Save values for later inspection
record(price=price,
cash=context.portfolio.cash,
price_change=price_change,
short_mavg=short_mavg,
long_mavg=long_mavg)
# Since we are using limit orders, some orders may not execute
# immediately we wait until all orders are executed before
# considering more trades.
orders = context.blotter.open_orders
if len(orders) > 0:
return
# Exit if we cannot trade
if not data.can_trade(context.asset):
return
# We check what's our position on our portfolio
# and trade accordingly
pos_amount = context.portfolio.positions[context.asset].amount
# Trading logic
if short_mavg > long_mavg and pos_amount == 0:
# we buy 100% of our portfolio for this asset
order_target_percent(context.asset, 1)
elif short_mavg < long_mavg and pos_amount > 0:
# we sell all our positions for this asset
order_target_percent(context.asset, 0)
def analyze(context, perf):
# Get the quote_currency that was passed as a parameter
# to the simulation
exchange = list(context.exchanges.values())[0]
quote_currency = exchange.quote_currency.upper()
# First chart: Plot portfolio value using quote_currency
ax1 = plt.subplot(411)
perf.loc[:, ['portfolio_value']].plot(ax=ax1)
ax1.legend_.remove()
ax1.set_ylabel('Portfolio Value\n({})'.format(quote_currency))
start, end = ax1.get_ylim()
ax1.yaxis.set_ticks(np.arange(start, end, (end - start) / 5))
# Second chart: Plot asset price, moving averages and buys/sells
ax2 = plt.subplot(412, sharex=ax1)
perf.loc[:, ['price', 'short_mavg', 'long_mavg']].plot(
ax=ax2,
label='Price')
ax2.legend_.remove()
ax2.set_ylabel('{asset}\n({quote})'.format(
asset=context.asset.symbol,
quote=quote_currency
))
start, end = ax2.get_ylim()
ax2.yaxis.set_ticks(np.arange(start, end, (end - start) / 5))
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=''
)
# Third chart: Compare percentage change between our portfolio
# and the price of the asset
ax3 = plt.subplot(413, sharex=ax1)
perf.loc[:, ['algorithm_period_return', 'price_change']].plot(ax=ax3)
ax3.legend_.remove()
ax3.set_ylabel('Percent Change')
start, end = ax3.get_ylim()
ax3.yaxis.set_ticks(np.arange(start, end, (end - start) / 5))
# Fourth chart: Plot our cash
ax4 = plt.subplot(414, sharex=ax1)
perf.cash.plot(ax=ax4)
ax4.set_ylabel('Cash\n({})'.format(quote_currency))
start, end = ax4.get_ylim()
ax4.yaxis.set_ticks(np.arange(0, end, end / 5))
plt.show()
if __name__ == '__main__':
run_algorithm(
capital_base=1000,
data_frequency='daily',
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='bitfinex',
algo_namespace=NAMESPACE,
quote_currency='usd',
start=pd.to_datetime('2016-1-1', utc=True),
end=pd.to_datetime('2018-6-4', utc=True),
)
Before you run this strategy, you need to have the daily price and volume data for Bitfinex stored on your local machine to stream through. This can be achieved by running catalyst ingest-exchange -x bitfinex
and waiting a little bit. Additional parameters you can specify include data frequency, start and end dates, and specific symbols if you so desire (you can view the other parameters by entering catalyst ingest-exchange --help
). I purposely left off the symbols parameter so that you can easily change the value for context.asset
above and iterate over other products.
The specifics of the strategy are commented in the code block above so please read through those for added context, but the big things to note are the higher level methods and definitions:
run_algorithm
: defines critical parameters to run the trading algorithm, such as starting capital, quote currency, exchange, and date range.initialize
: called once at the start of the whole run and used to define context variables that maintain state.handle_data
: run during every iteration of the algorithm depending on the frequency (daily, minute).analyze
: called at the end of the algorithm’s full run and contains the performance data frame to be studied.
You are now ready to run this strategy from PyCharm, simply by clicking the green arrow on the top right of the IDE. After a just few seconds, the strategy will complete running and generate performance plots like so:
As the charts demonstrate, this simple momentum strategy is a profitable one (we started with a base capital of only $1000) and effectively captured most of the large moves during the bull trend we’ve experienced.
Any of this not working perfectly? Click here to get help on the Catalyst forum | Click here to ask our team in Discord!
Conclusion
That’s it for now — you’ve just completed your first profitable, automated strategy to trade BTC on a leading crypto exchange! In the next post, I will demonstrate the necessary steps to leverage the power of the Data Marketplace to further enhance your strategy’s performance. I’ll also cover how you can save off the performance data frame to a Jupyter notebook and generate some nicer plots and analysis than comes out of the box.
Catalyst is still in its alpha stage and under rapid development, so if you have any questions, comments, concerns, feedback, or just want to say hey, please take a look at our full documentation and feel free to drop us a note in our Discord channel or the forums!