Quantification of the efficiency of trading ETH/BTC or LTC/BTC using simple moving averages
from IPython.display import HTML
HTML('''<script>
code_show=false;
function code_toggle() {
if (code_show){
$('div.input').show();
$('div.output_prompt').show();
} else {
$('div.input').hide();
$('div.output_prompt').hide();
}
code_show =! code_show
}
$( document ).ready(code_toggle);
</script>
<font> This analysis was made using Python. If you'd like to see the code used, click <a href="javascript:code_toggle()">here</a>.</font>
''')
Introduction¶
This notebook shows the process of investigating the price history of Bitcoin, Ethereum and Litecoin using Simple Moving Averages (SMAs).
I noticed that the 7 day and 30 day SMAs would cross each other occasionally, and I wondered how profitable it would be to use this as a trading strategy.
I generate a heat map to show how profitability varies across different pairs of SMAs.
For a given SMA pair I show the trading algorithms performance between two dates.
Setup and import data¶
Setup involves importing the python packages required and changing the default notebook settings. I use Plotly figures rather than a simpler method of visualising data and whilst creating the notebook I use the offline Plotly options. If I use other visualisation packages I’ll set figures to appear below the code cell that called the plot command.
The price data is downloaded from Quandl. In order to keep my Quandl and Plot.ly credentials private, I keep my account credentials in a separate .py file.
The Pickle package and get_data() functions are used to download Quandl data only once and then store it locally in a .pkl file. This is quicker than downloading it every time I (re)run the notebook.
## Setup - libraries
%matplotlib inline
import os
import pickle
import quandl
import matplotlib.pyplot as plt
import pandas as pd
import datetime as dt
import numpy as np
import credentials # keep my quandl and plot.ly api keys private
import plotly
#import plotly.offline as py
import plotly.plotly as py
import plotly.graph_objs as go
import plotly.figure_factory as ff
#py.init_notebook_mode(connected=True)
plotly.offline.init_notebook_mode()
#from mpl_toolkits.mplot3d import Axes3D
## Setup - appearance
# get rid of the annoying 'SettingWithCopy' warning
pd.options.mode.chained_assignment = None # default='warn'
# more than one print of an unassigned variable
from IPython.core.interactiveshell import InteractiveShell;
InteractiveShell.ast_node_interactivity = "all";
# offline plotly
plotly.offline.init_notebook_mode()
color1 = 'red'
color2 = '#137a28' # dark green
# Helper function for pulling data from quandl
def get_data(quandl_id):
'''Download and cache Quandl data series'''
cache_path = '{}.pkl'.format(quandl_id).replace('/','-')
try:
f = open(cache_path, 'rb')
df = pickle.load(f)
#print('Loaded {} from cache'.format(quandl_id))
except (OSError, IOError) as e:
#print('Downloading {} from Quandl'.format(quandl_id))
df = quandl.get(quandl_id, returns="pandas")
df.to_pickle(cache_path)
#print('Cached {} at {}'.format(quandl_id, cache_path))
return df
# Quandle codes for each dataset.
data_sets = ['ltc_btc','eth_btc','gbp','EURGBP']
# Cool loop to define variables.
dict_of_dfs = {}
for item in data_sets:
data_code = '{}'.format(item)
if item == "EURGBP":
dataset = get_data( 'ECB/{}'.format(item) )
else:
dataset = get_data( 'GDAX/{}'.format(item) )
dict_of_dfs[item] = dataset
Format data¶
After importing the data it needs to be changed into a convenient form. In this notebook I download the price of Bitcoin in GBP, and the price of Ethereum and Litecoin in Bitcoin. Each of these is a different dataset on Quandl, so I copied the relevant data from each data set into one new data frame that contained everything I was interested in.
The price of Ethereum or Litecoin in GBP is the product of their respective prices in Bitcoin and Bitcoin’s price in GBP. I calculated this and created new columns to store the result.
# helper_func to take one column from many dfs and merge into a single new df
def one_col_from_each_df(dict_of_dfs, col):
'''Merge a single column of each dataframe into a new combined dataframe'''
series_dict = {}
for key in dict_of_dfs:
series_dict[key] = dict_of_dfs[key][col]
return pd.DataFrame(series_dict)
dict_of_dfs["EURGBP"]['Open'] = dict_of_dfs["EURGBP"]['Value']
# Merge opening price for each currency into a single dataframe
df = one_col_from_each_df(dict_of_dfs, 'Open')
#df.tail()
df.loc['2017-03-01':'2017-03-02']['ltc_btc'] = np.NaN
#df.loc['2017-03-01':'2017-03-02']['ltc_btc']
# convert to GBP and rename columns
df['btc'] = df['gbp']
df['eth'] = df['gbp'] * df['eth_btc']
df['ltc'] = df['gbp'] * df['ltc_btc']
df['eur'] = df['EURGBP']
#df = df.drop(['gbp','eth_btc','ltc_btc','EURGBP'], axis=1)
# put price data in its own df ("prices") to do growth analysis later
# keep prices in GBP because GBP varies less than any other common measure.
prices = df.loc['2016-01-01':,['btc', 'eth', 'ltc', 'eth_btc', 'ltc_btc']]
prices = prices.interpolate(method='time')
#end = df.iloc[-1:].index[0]
#df = df.loc['2017-06-21':end]
# Bitcoin price
series1 = go.Scatter(
x=prices.index.get_level_values('Date'),
y=prices['btc'],
name='Price',
line = dict(
color = ('green'),
width = 2))
series2 = go.Scatter(
x=prices.index.get_level_values('Date'),
y=prices['btc'].rolling(window=7).mean(),
name='7 day SMA',
line = dict(
color = ('blue'),
width = 1))
series3 = go.Scatter(
x=prices.index.get_level_values('Date'),
y=prices['btc'].rolling(window=30).mean(),
name='30 day SMA',
line = dict(
color = ('red'),
width = 1))
data = [series1, series2, series3]
layout = go.Layout(
title='Bitcoin price',
yaxis=dict(title='GBP',
tickformat=""),
legend=dict(orientation="h", yanchor='top', y=1.1, xanchor='center', x=0.5)
)
fig = go.Figure(data=data, layout=layout)
#plotly.offline.iplot(fig, filename='btc_price')
py.iplot(fig, filename='btc_price')
Ethereum - £¶
#end = df.iloc[-1:].index[0]
#df = df.loc['2017-06-21':end]
# Ethereum price
series1 = go.Scatter(
x=prices.index.get_level_values('Date'),
y=prices['eth'],
name='ETH',
line = dict(
color = ('green'),
width = 2))
series2 = go.Scatter(
x=prices.index.get_level_values('Date'),
y=prices['eth'].rolling(window=7).mean(),
name='7 day SMA',
line = dict(
color = ('blue'),
width = 1))
series3 = go.Scatter(
x=prices.index.get_level_values('Date'),
y=prices['eth'].rolling(window=30).mean(),
name='30 day SMA',
line = dict(
color = ('red'),
width = 1))
data = [series1, series2, series3]
layout = go.Layout(
title='Ethereum price',
yaxis=dict(title='GBP',
tickformat=""),
legend=dict(orientation="h", yanchor='top', y=1.1, xanchor='center', x=0.5)
)
fig = go.Figure(data=data, layout=layout)
#plotly.offline.iplot(fig, filename='ethPrice')
py.iplot(fig, filename='ethPrice')
Ethereum - BTC¶
#end = df.iloc[-1:].index[0]
#df = df.loc['2017-06-21':end]
#prices = df.loc['2017-01-01':,['btc', 'eth', 'ltc', 'eth_btc', 'ltc_btc']]
# Ethereum price
series1 = go.Scatter(
x=prices.index.get_level_values('Date'),
y=prices['eth_btc'],
name='ETH/BTC',
line = dict(
color = ('green'),
width = 2))
series2 = go.Scatter(
x=prices.index.get_level_values('Date'),
y=prices['eth_btc'].rolling(window=7).mean(),
name='7 day SMA',
line = dict(
color = ('blue'),
width = 1))
series3 = go.Scatter(
x=prices.index.get_level_values('Date'),
y=prices['eth_btc'].rolling(window=30).mean(),
name='30 day SMA',
line = dict(
color = ('red'),
width = 1))
data = [series1, series2, series3]
layout = go.Layout(
title='Ethereum / Bitcoin',
yaxis=dict(title='BTC',
tickformat=""),
legend=dict(orientation="h", yanchor='top', y=1.1, xanchor='center', x=0.5)
)
fig = go.Figure(data=data, layout=layout)
#plotly.offline.iplot(fig, filename='ethBtcPrice')
py.iplot(fig, filename='ethBtcPrice')
LiteCoin - £¶
#end = df.iloc[-1:].index[0]
#df = df.loc['2017-01-01':end]
# Litecoin price
series1 = go.Scatter(
x=prices.index.get_level_values('Date'),
y=prices['ltc'],
name='LTC',
line = dict(
color = ('green'),
width = 2))
series2 = go.Scatter(
x=prices.index.get_level_values('Date'),
y=prices['ltc'].rolling(window=7).mean(),
name='7 day SMA',
line = dict(
color = ('blue'),
width = 1))
series3 = go.Scatter(
x=prices.index.get_level_values('Date'),
y=prices['ltc'].rolling(window=30).mean(),
name='30 day SMA',
line = dict(
color = ('red'),
width = 2))
data = [series1, series2, series3]
layout = go.Layout(
title='Litecoin price',
yaxis=dict(title='GBP',
tickformat=""),
legend=dict(orientation="h", yanchor='top', y=1.1, xanchor='center', x=0.5)
)
fig = go.Figure(data=data, layout=layout)
#plotly.offline.iplot(fig, filename='ltcPrice')
py.iplot(fig, filename='ltcPrice')
Litecoin - BTC¶
# end = df.iloc[-1:].index[0]
# df = df.loc['2017-01-01':end]
win1 = 7
win2 = 30
# Litecoin price
series1 = go.Scatter(
x=prices.index.get_level_values('Date'),
y=prices['ltc_btc'],
name='LTC/BTC',
line = dict(
color = ('green'),
width = 1))
series2 = go.Scatter(
x=prices.index.get_level_values('Date'),
y=prices['ltc_btc'].rolling(window=win1).mean(),
name='{} day SMA'.format(win1),
line = dict(
color = ('blue'),
width = 2))
series3 = go.Scatter(
x=prices.index.get_level_values('Date'),
y=prices['ltc_btc'].rolling(window=win2).mean(),
name='{} day SMA'.format(win2),
line = dict(
color = ('red'),
width = 2))
data = [series1, series2, series3]
layout = go.Layout(
title='Litecoin / Bitcoin',
yaxis=dict(title='BTC',
tickformat=""),
legend=dict(orientation="h", yanchor='top', y=1.1, xanchor='center', x=0.5)
)
fig = go.Figure(data=data, layout=layout)
#plotly.offline.iplot(fig, filename='ltcBtcPrice')
py.iplot(fig, filename='ltcBtcPrice')
SMA Analysis¶
The price data for the digital assets above shows a high degree of variance, so when I first visualized the price history I wanted to smooth it somehow. I plotted a simple moving average and became curious what different types of average would look like. I noticed that the longer and shorter SMAs (Simply Moving Averages) cross each other occasionally and I wondered if this would make a useful trading algorithm.
The code below (if it’s hidden, click the ‘show code’ link at the beginning of post) shows how to calculate SMAs, identify when two time series cross each other, and calculate the returns from buying or selling depending on when the long SMA moves above the short SMA, or when short moves above long.
I wanted to know which combination of SMA periods would yield the best results, and the heat maps show this.
Finally, for a given date range, asset pair and short and long SMA combination, I plotted the performance of the trading strategy with those parameters through time. This was to see if the algorithms performance was consistent, or if large losses could occur. It would also show how much upfront cost would have been needed to realise the returns.
Net gains w/ different SMA combinations¶
# identify where the two SMAs cross over each other
# return the dates where this occurs, and label them buy or sell
def crossOver(pair, sma1, sma2):
if sma2 < sma1:
sma1, sma2 = sma2, sma1
df = pd.DataFrame()
df['btcPrice'] = prices['btc']
df['data'] = prices[pair]
df['sma1'] = df['data'].rolling(window=sma1).mean()
df['sma2'] = df['data'].rolling(window=sma2).mean()
df['diff'] = df['sma1'] - df['sma2']
df['inGBP'] = df['btcPrice'] * df['data']
# sell ltc for btc when diff < 0 && cross == True
# buy ltc with btc when diff > 0 && cross == True
line = 0
df = df.dropna()
df['nextDiff'] = df['diff'].shift(-1)
df['cross'] = (((df['diff'] >= line) & (df['nextDiff'] < line)) | ((df['nextDiff'] > line) & (df['diff'] <= line)) | (df['diff'] == line))
rows = df.loc[df['cross'] == True]
rows['trade'] = '...'
rows['trade'][ (rows['cross'] == True) & (rows['diff'] > 0) ] = 'sell'
rows['trade'][ (rows['cross'] == True) & (rows['diff'] < 0) ] = 'buy'
# df = all the data
# rows = just the rows of df where SMA1 crosses SMA2
out = {'data':df, 'xOver':rows}
return out
#crossOver('ltc_btc', 3, 8)['data'].tail()
# take the crossOver data and calculate how much would you would gain or lose between
# the selected dates
def returns(pair, sma1, sma2, dt1, dt2 = dt.datetime.today().strftime('%Y-%m-%d')):
# make sure dt1, dt2 are correctly formatted!
data = crossOver(pair, sma1, sma2) # crossOver returns a dictionary with 2 items
trades=data['xOver'] # just the rows where SMA1 crosses SMA2
# just the rows between the dates we're interested in
trades = trades.loc[dt1:dt2]
# must start with a buy. delete the first row if its a sell
if trades.iloc[0]['trade'] != 'buy':
trades = trades.drop(trades.index[0])
# calc the profit
# make nice labels for the return dict
count=len(trades.index)
buys = trades[trades['trade']=='buy']['data'].sum()
sells = trades[trades['trade']=='sell']['data'].sum()
buysGBP = trades[trades['trade']=='buy']['inGBP'].sum()
sellsGBP = trades[trades['trade']=='sell']['inGBP'].sum()
p = sells - buys
pGBP = sellsGBP - buysGBP
results = {'pGBP': pGBP, 'profit': p,'trades': count,'sum of buys': buys,'sum of sells': sells, 'data':trades, 'pair':pair}
return results
# This function calls the other two functions (defined above)
# Input the asset pair, start and finish dates, and the range of SMAs to calc
# Returns an ok-ish heatmap
def smaArray(pltlyName, pair, maxDays, dt1, dt2 = dt.datetime.today().strftime('%Y-%m-%d')):
tbl = np.zeros((maxDays, maxDays))
for i in range(maxDays):
for j in range(maxDays):
if j<=i:
tbl[i,j] = np.NaN
else:
tbl[i,j] = returns(pair,i+1,j+1,dt1,dt2)['pGBP'] #alternatively: ['proift']
#tbl
trace = go.Heatmap(z=tbl,
x=list(range(1,maxDays+1)),
y=list(range(1,maxDays+1)),
)
data=[trace]
#py.iplot(data, filename='basic-heatmap')
layout = go.Layout(
title='{} (GBP)'.format(pair),
yaxis=dict(title='SMA1'),
xaxis=dict(title='SMA2')
)
fig = go.Figure(data=data, layout=layout)
#out = plotly.offline.iplot(fig, filename='smaAnalyis_{}'.format(pair))
out = py.iplot(fig, filename='smaAnalyis_{}_{}'.format(pair,maxDays))
return out
smaArray(pair='ltc_btc', maxDays=30, dt1='2017-01-01', pltlyName='ltc_btc_30')
smaArray(pair='eth_btc', maxDays=30, dt1='2017-01-01', pltlyName='eth_btc_30')
Returns through time for one combination of sma1 and sma2¶
# This function creates a plot showing the profit through time for a given input
def time(pltly_name, pair, sma1, sma2, dt1, dt2 = dt.datetime.today().strftime('%Y-%m-%d')):
out = returns(pair, sma1, sma2, dt1)
ts = out['data']
ts['data'] = np.where(ts['trade'] == 'buy', ts['data'] * -1, ts['data'])
ts['dataGBP'] = np.where(ts['trade'] == 'buy', ts['inGBP'] * -1, ts['inGBP'])
ts['returns'] = ts['data'].cumsum()
ts['returnsGBP'] = ts['dataGBP'].cumsum()
ts['returns_av'] = ts['returns'].rolling(window=2).mean()
ts['returnsGBP_av'] = ts['returnsGBP'].rolling(window=2).mean()
#ts
series1 = go.Scatter(
x=ts.index.get_level_values('Date'),
y=ts['returnsGBP'],
name='{}'.format(out['pair']),
line = dict(
color = ('blue'),
width = 1))
series2 = go.Scatter(
x=ts.index.get_level_values('Date'),
y=ts['returnsGBP_av'],
name='av',
line = dict(
color = ('#137a28'),
width = 2))
data = [series1, series2]
layout = go.Layout(
title='{}: sma1 = {}d, sma2 = {}d'.format(pair, sma1, sma2 ),
yaxis=dict(title='GBP',
tickformat=""),
showlegend=False,
legend=dict(orientation="h", yanchor='top', y=1.1, xanchor='center', x=0.5)
)
fig = go.Figure(data=data, layout=layout)
plot = py.iplot(fig, filename='{}'.format(pltly_name))
#plot = plotly.offline.iplot(fig, filename='tradingResults')
results = {'data': ts , 'plot':plot }
return results
time(pair='ltc_btc', sma1=8, sma2=5, dt1='2017-01-15', pltly_name = '8-5,ltc_btc')['plot'] #['data'].tail()
time(pltly_name='10-6_eth_btc',pair='eth_btc', sma1=10, sma2=6, dt1='2017-01-15')['plot'] #['data'].tail()
Next steps:¶
Create a bot to monitor real time price data, calculate the moving averages, and place trades.
If you’d like to collaborate with me to do this, please contact me.