Bitcoin Technical Analysis

Introdution

This notebook includes conducting a technical analysis on Bitcoin Data. Cryptocurrency markets are highly volatile and although, 70-80% of it is based upon sentiments and social media, an extensive technical analysis might provide some insights into how the trend works. For now, 2 datasets have been used, one of which contains 1-minute data of Bitcoin and the other one contains daily Bitcoin data.

The technical analysis metrics consists of

  1. Moving Averages - This metric can be used to get a decent idea of the asset's established trend. This metric can't be used to predict future trends.

  2. Average True Range - It is the average of the true ranges over the specified period. It measures the volatility in the market. A typical ATR calculation is of 14 days.

  3. Relative Strength Index - The tool measures the strength of the dynamics and the trend of the digital asset. In other terms, it can be used to predict the momentum and the divergence of the asset.

  4. Bollinger Bands - It is useful to in measuring volatility and trends. The upper band is 2 standard deviation above mean and the lower band is 2 standard deviation below mean.

1. Imports

1.1 Packages

import warnings
import numpy as np
import pandas as pd
import seaborn as sns
import plotly.express as px
from termcolor import colored
import matplotlib.pyplot as plt
import plotly.graph_objects as go
warnings.filterwarnings("ignore")
from plotly.subplots import make_subplots
print('Packages successfully imported...')
Packages successfully imported...

1.2 Dataset

df = pd.read_parquet('/kaggle/input/binance-full-history/BTC-USDT.parquet')

# The 1-day data for Bitcoin
daily = pd.read_csv('../input/cryptocurrency-data/BTC-USD-2.csv', parse_dates = [0])

print('Data successfully imported...')
Data successfully imported...

2. Understanding Data

2.1 The 1-min data

2.1.1 Head
df.head()
open high low close volume quote_asset_volume number_of_trades taker_buy_base_asset_volume taker_buy_quote_asset_volume
open_time
2017-08-17 04:00:00 4261.479980 4261.479980 4261.479980 4261.479980 1.775183 7564.906738 3 0.075183 320.390839
2017-08-17 04:01:00 4261.479980 4261.479980 4261.479980 4261.479980 0.000000 0.000000 0 0.000000 0.000000
2017-08-17 04:02:00 4280.560059 4280.560059 4280.560059 4280.560059 0.261074 1117.542969 2 0.261074 1117.542969
2017-08-17 04:03:00 4261.479980 4261.479980 4261.479980 4261.479980 0.012008 51.171852 3 0.012008 51.171852
2017-08-17 04:04:00 4261.479980 4261.479980 4261.479980 4261.479980 0.140796 599.999329 1 0.140796 599.999329
2.1.2 Preprocessing
df.index.rename('date',inplace=True)
df.drop(['quote_asset_volume','taker_buy_base_asset_volume','taker_buy_quote_asset_volume'],inplace = True,axis=1)
2.1.3 Checking Null Values
df.isnull().sum()
open                0
high                0
low                 0
close               0
volume              0
number_of_trades    0
dtype: int64

2.2 Daily Data

2.2.1 Head
daily.head()
Date Open High Low Close Adj Close Volume
0 2020-06-16 9454.266602 9579.430664 9400.445313 9538.024414 9538.024414 2.156554e+10
1 2020-06-17 9533.784180 9540.422852 9327.339844 9480.254883 9480.254883 2.017771e+10
2 2020-06-18 9481.567383 9482.782227 9328.395508 9411.840820 9411.840820 1.777008e+10
3 2020-06-19 9410.293945 9440.875977 9274.295898 9288.018555 9288.018555 1.963222e+10
4 2020-06-20 9290.959961 9394.971680 9247.379883 9332.340820 9332.340820 1.713054e+10
2.2.2 Set date as index
daily.set_index('Date',inplace=True)
2.2.3 Checking for null values
daily.isnull().sum()
Open         3
High         3
Low          3
Close        3
Adj Close    3
Volume       3
dtype: int64
2.2.4 Removing null values
daily.fillna(method='ffill',inplace=True, axis=0)
daily.isnull().sum()
Open         0
High         0
Low          0
Close        0
Adj Close    0
Volume       0
dtype: int64

2.3 Heads after preprocessing

2.3.1 Daily data
daily.head()
Open High Low Close Adj Close Volume
Date
2020-06-16 9454.266602 9579.430664 9400.445313 9538.024414 9538.024414 2.156554e+10
2020-06-17 9533.784180 9540.422852 9327.339844 9480.254883 9480.254883 2.017771e+10
2020-06-18 9481.567383 9482.782227 9328.395508 9411.840820 9411.840820 1.777008e+10
2020-06-19 9410.293945 9440.875977 9274.295898 9288.018555 9288.018555 1.963222e+10
2020-06-20 9290.959961 9394.971680 9247.379883 9332.340820 9332.340820 1.713054e+10
2.3.2 1-minute data
df.head()
open high low close volume number_of_trades
date
2017-08-17 04:00:00 4261.479980 4261.479980 4261.479980 4261.479980 1.775183 3
2017-08-17 04:01:00 4261.479980 4261.479980 4261.479980 4261.479980 0.000000 0
2017-08-17 04:02:00 4280.560059 4280.560059 4280.560059 4280.560059 0.261074 2
2017-08-17 04:03:00 4261.479980 4261.479980 4261.479980 4261.479980 0.012008 3
2017-08-17 04:04:00 4261.479980 4261.479980 4261.479980 4261.479980 0.140796 1

3. Analysing Daily data

3.1 Candlestick plot

fig = go.Figure(data=[go.Candlestick(x=daily.index,
                open=daily['Open'],
                high=daily['High'],
                low=daily['Low'],
                close=daily['Close'])], 
                layout = go.Layout({
                    'title':{
                    'text':'Bitcoin Daily data',
                    'font':{
                        'size':25
                            }
                        }
                    })
               )


fig.update_layout({"template":"plotly_dark"})
fig.show()

3.2 Moving Averages

$$ MA = 1/n * \sum_{n=x_i}^{x_n} Prices $$

Moving average cuts down the noise in the price chart. The direction of the MA might give an idea of the trend of the chart. A subtle observation can be made from the plot that is whenever the candlestick cuts the MA threshold level to rise above it, the price is likely to go high for some duration of time before which there's a change in direction of price to due to other factors. In the same way, when the candlestick cuts the MA threshold level to rise below it, the price is likely to fall for some duration. At the rising cut-point, a trader can enter the market and at the falling cut-point, a trader might sell. This is also called as a crossover.

avg_20 = daily.Close.rolling(window=20, min_periods=1).mean()
avg_50 = daily.Close.rolling(window=50, min_periods=1).mean()
avg_100 = daily.Close.rolling(window=100, min_periods=1).mean()

set_1 = {
    'x':daily.index,
    'open': daily.Open,
    'close':daily.Close,
    'high':daily.High,
    'low':daily.Low,
    'type':'candlestick'
}

set_2 = {
    'x':daily.index,
    'y':avg_20,
    'type':'scatter',
    'mode':'lines',
    'line':{
        'width':2,
        'color':'white'
    },
    'name':'Moving average of 20 days'
}

set_3 = {
    'x':daily.index,
    'y':avg_50,
    'type':'scatter',
    'mode':'lines',
    'line':{
        'width':2,
        'color':'yellow'
    },
    'name':'Moving average of 50 days'
}

set_4 = {
    'x':daily.index,
    'y':avg_100,
    'type':'scatter',
    'mode':'lines',
    'line':{
        'width':2,
        'color':'orange'
    },
    'name':'Moving average of 100 days'
}

data = [set_1, set_2, set_3, set_4]

layout = go.Layout({
    'title':{
        'text':'Moving averages',
        'font':{
            'size':25
        }
    }
})

fig = go.Figure(data=data, layout=layout)
fig.update_layout({"template":"plotly_dark"})
fig.show()

3.3 Average True Range

$$ TR = MAX([(H-L), Abs(H-Cp), Abs(L-Cp)]) $$$$ ATR = 1/n * \sum_{n=x_i}^{x_n} TR $$

The true range indicator is taken as the greatest of the following: current high less the current low; the absolute value of the current high less the previous close; and the absolute value of the current low less the previous close. The ATR is then a moving average, generally using 14 days, of the true ranges. Simply put, a stock experiencing a high level of volatility has a higher ATR, and a low volatility stock has a lower ATR.

high_low = daily['High'] - daily['Low']
high_close = np.abs(daily['High'] - daily['Close'].shift())
low_close = np.abs(daily['Low'] - daily['Close'].shift())

ranges = pd.concat([high_low, high_close, low_close], axis=1)
true_range = np.max(ranges, axis=1)

atr = true_range.rolling(14).sum()/14

atr_daily = daily
atr_daily['atr'] = atr

atr_daily['High_atr'] = atr_daily['High'] + atr_daily['atr']
atr_daily['Low_atr'] = atr_daily['Low'] - atr_daily['atr']

fig = make_subplots(rows=2, cols=1, row_heights=[0.5, 0.5], vertical_spacing=.3, subplot_titles=["Bitcoin Daily Data", "Average True Range"])

fig.append_trace(go.Candlestick(x=daily.index,
                open=atr_daily['Open'],
                high=atr_daily['High'],
                low=atr_daily['Low'],
                close=atr_daily['Close']),
                row=1,col=1)

fig.append_trace(go.Scatter(x=atr_daily.index,
                           y=atr_daily.atr,
                            line_color='Orange'
            ),row=2,col=1)



fig.update_layout(
    margin=dict(l=20, r=20, t=20, b=20),
    
)
fig.update_layout({"template":"plotly_dark"})


fig.show()
3.3.1 ATR Significance

Though ATR doesn't give much of an idea about the trend of the chart, but it can be used by traders to put stop losses. An ATR value above close or high can be used as a potential exit point. An ATR value below open or low can be used as a potential exit point as well in case of down fall of the price.

set_1 = go.Candlestick(x=daily.index,
                open=atr_daily['Open'],
                high=atr_daily['High'],
                low=atr_daily['Low'],
                close=atr_daily['Close'])


set_2 = go.Scatter(x=atr_daily.index,
        y=atr_daily.High_atr,
        line_color='Orange')


set_3 = go.Scatter(x=atr_daily.index,
        y=atr_daily.Low_atr,
        line_color='Yellow')


data = [set_1, set_2, set_3]

layout = go.Layout({
    'title':{
        'text':'Upper/Lower limit of ATR values for trading',
        'font':{
            'size':25
        }
    }
})

fig = go.Figure(data=data, layout=layout)
fig.update_layout({"template":"plotly_dark"})
fig.show()