bitcoin trading
Trading digital assets
October 2017

Quantification of the efficiency of trading ETH/BTC or LTC/BTC using simple moving averages

In [1]:
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>
''')
Out[1]:
This analysis was made using Python. If you’d like to see the code used, click here.

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.

In [2]:
## 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
In [3]:
## 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
In [4]:
# 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
In [5]:
# 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.

In [6]:
# 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)
In [7]:
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)
In [8]:
# 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']]
In [9]:
prices = prices.interpolate(method='time')

Asset charts

Bitcoin - £

In [10]:
#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')
Out[10]:

Ethereum - £

In [11]:
#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')
Out[11]:

Ethereum - BTC

In [12]:
#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')
Out[12]:

LiteCoin - £

In [13]:
#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')
Out[13]:

Litecoin - BTC

In [14]:
# 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')
Out[14]:

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

In [15]:
# 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
In [16]:
#crossOver('ltc_btc', 3, 8)['data'].tail()
In [17]:
# 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
In [18]:
# 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
In [19]:
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')
Out[19]:
Out[19]:

Returns through time for one combination of sma1 and sma2

In [20]:
# 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
In [21]:
time(pair='ltc_btc', sma1=8, sma2=5, dt1='2017-01-15', pltly_name = '8-5,ltc_btc')['plot'] #['data'].tail()
Out[21]:
In [22]:
time(pltly_name='10-6_eth_btc',pair='eth_btc', sma1=10, sma2=6, dt1='2017-01-15')['plot'] #['data'].tail()
Out[22]:

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.