{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Cryptocurrency Analysis with Python - MACD" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Cryptocurrencies are becoming mainstream so I've decided to spend the weekend learning about it. \n", "I've hacked together the \n", "[code]({{site.url}}/assets/notebooks/2017-12-10-cryptocurrency-analysis-with-python-part1.ipynb)\n", "to download daily Bitcoin prices and apply a simple trading strategy to it. \n", "\n", "Note that there already exists tools for performing this kind of analysis, eg. \n", "[tradeview](https://www.tradingview.com/), but this way enables more in-depth analysis.\n", "\n", "Check out my [next blog post]({{site.url}}/cryptocurrency/analysis/2017/12/25/cryptocurrency-analysis-with-python-part2.html), \n", "where I describe buy and hold strategy and follow me on [twitter](https://twitter.com/romanorac) to get latest updates.\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Disclaimer\n", "I am not a trader and this blog post is not a financial advice. This is purely introductory knowledge." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Requirements\n", "\n", "- Python 3\n", "\n", "- [Jupyter Notebook](http://jupyter.org/)\n", "\n", "- [Pandas Data Analysis Library](https://pandas.pydata.org/) \n", "\n", "- [Bokeh interactive visualization library](https://bokeh.pydata.org/en/latest/)\n", "\n", "- [stock Statistics/Indicators Calculation Helper](https://github.com/jealous/stockstats)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Getting cryptocurrency data\n", "\n", "We download daily Bitcoin data in USD on Bitstamp exchange. [Other exchanges](https://www.cryptocompare.com/api/#introduction) are also supported. " ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "from_symbol = 'BTC'\n", "to_symbol = 'USD'\n", "exchange = 'Bitstamp'\n", "datetime_interval = 'day'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The [cryptocompare api](https://www.cryptocompare.com/api/#introduction) returns following columns:\n", " - **open**, the price at which the period opened,\n", " - **high**, the highest price reached during the period,\n", " - **low**, the lowest price reached during the period,\n", " - **close**, the price at which the period closed,\n", " - **volumefrom**, the volume in the base currency that things are traded into,\n", " - **volumeto**, the volume in the currency that is being traded.\n", " \n", "We download the data and store it to a file." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Downloading day trading data for BTC USD from Bitstamp\n", "Filtering 877 empty datapoints\n", "Saving data to BTC_USD_Bitstamp_day_2017-12-25.csv\n" ] } ], "source": [ "import requests\n", "from datetime import datetime\n", "\n", "\n", "def get_filename(from_symbol, to_symbol, exchange, datetime_interval, download_date):\n", " return '%s_%s_%s_%s_%s.csv' % (from_symbol, to_symbol, exchange, datetime_interval, download_date)\n", "\n", "\n", "def download_data(from_symbol, to_symbol, exchange, datetime_interval):\n", " supported_intervals = {'minute', 'hour', 'day'}\n", " assert datetime_interval in supported_intervals, 'datetime_interval should be one of %s' % supported_intervals\n", "\n", " print('Downloading %s trading data for %s %s from %s' %\n", " (datetime_interval, from_symbol, to_symbol, exchange))\n", " base_url = 'https://min-api.cryptocompare.com/data/histo'\n", " url = '%s%s' % (base_url, datetime_interval)\n", "\n", " params = {'fsym': from_symbol, 'tsym': to_symbol,\n", " 'limit': 2000, 'aggregate': 1,\n", " 'e': exchange}\n", " request = requests.get(url, params=params)\n", " data = request.json()\n", " return data\n", "\n", "\n", "def convert_to_dataframe(data):\n", " df = pd.io.json.json_normalize(data, ['Data'])\n", " df['datetime'] = pd.to_datetime(df.time, unit='s')\n", " df = df[['datetime', 'low', 'high', 'open',\n", " 'close', 'volumefrom', 'volumeto']]\n", " return df\n", "\n", "\n", "def filter_empty_datapoints(df):\n", " indices = df[df.sum(axis=1) == 0].index\n", " print('Filtering %d empty datapoints' % indices.shape[0])\n", " df = df.drop(indices)\n", " return df\n", "\n", "\n", "data = download_data(from_symbol, to_symbol, exchange, datetime_interval)\n", "df = convert_to_dataframe(data)\n", "df = filter_empty_datapoints(df)\n", "\n", "current_datetime = datetime.now().date().isoformat()\n", "filename = get_filename(from_symbol, to_symbol, exchange, datetime_interval, current_datetime)\n", "print('Saving data to %s' % filename)\n", "df.to_csv(filename, index=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Read the data\n", "\n", "We read the data from a file so we don't need to download it again." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Reading data from BTC_USD_Bitstamp_day_2017-12-25.csv\n", "(1124, 6)\n" ] } ], "source": [ "import pandas as pd\n", "\n", "def read_dataset(filename):\n", " print('Reading data from %s' % filename)\n", " df = pd.read_csv(filename)\n", " df.datetime = pd.to_datetime(df.datetime) # change type from object to datetime\n", " df = df.set_index('datetime') \n", " df = df.sort_index() # sort by datetime\n", " print(df.shape)\n", " return df\n", "\n", "df = read_dataset(filename)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Trading strategy\n", "\n", "A trading strategy is a set of objective rules defining the conditions that must be met for a trade entry and exit to occur. \n", "\n", "We are going to apply Moving Average Convergence Divergence (MACD) trading strategy, which is a popular indicator used in technical analysis. \n", "MACD calculates two moving averages of varying lengths to identify trend direction and duration.\n", "Then, it takes the difference in values between those two moving averages (MACD line) \n", "and an exponential moving average (signal line) of those moving averages.\n", "Tradeview has a great blog post about [MACD](https://www.tradingview.com/wiki/MACD_).\n", "\n", "As we can see in the example below:\n", "- exit trade (sell) when MACD line crosses below the MACD signal line,\n", "- enter trade (buy) when MACD line crosses above the MACD signal line. \n", "\n", "![]( http://www.onlinetradingconcepts.com/images/technicalanalysis/MACDbuysellaltNQ.gif \"MACD\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Calculate the trading strategy\n", "We use [stockstats](https://github.com/jealous/stockstats) package to calculate MACD." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "from stockstats import StockDataFrame\n", "df = StockDataFrame.retype(df)\n", "df['macd'] = df.get('macd') # calculate MACD" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "stockstats adds 5 columns to dataset:\n", "- **close_12_ema** is fast 12 days exponential moving average,\n", "- **close_26_ema** is slow 26 days exponential moving average,\n", "- **macd** is MACD line,\n", "- **macds** is signal line,\n", "- **macdh** is MACD histogram." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " | low | \n", "high | \n", "open | \n", "close | \n", "volumefrom | \n", "volumeto | \n", "close_12_ema | \n", "close_26_ema | \n", "macd | \n", "macds | \n", "macdh | \n", "
---|---|---|---|---|---|---|---|---|---|---|---|
datetime | \n", "\n", " | \n", " | \n", " | \n", " | \n", " | \n", " | \n", " | \n", " | \n", " | \n", " | \n", " |
2014-11-28 | \n", "360.57 | \n", "381.34 | \n", "363.59 | \n", "376.28 | \n", "8617.15 | \n", "3220878.18 | \n", "376.280000 | \n", "376.280000 | \n", "0.000000 | \n", "0.000000 | \n", "0.000000 | \n", "
2014-11-29 | \n", "372.25 | \n", "386.60 | \n", "376.42 | \n", "376.72 | \n", "7245.19 | \n", "2746157.05 | \n", "376.518333 | \n", "376.508462 | \n", "0.009872 | \n", "0.005484 | \n", "0.008775 | \n", "
2014-11-30 | \n", "373.32 | \n", "381.99 | \n", "376.57 | \n", "373.34 | \n", "3046.33 | \n", "1145566.61 | \n", "375.277829 | \n", "375.370064 | \n", "-0.092235 | \n", "-0.034565 | \n", "-0.115341 | \n", "
2014-12-01 | \n", "373.03 | \n", "382.31 | \n", "376.40 | \n", "378.39 | \n", "6660.56 | \n", "2520662.37 | \n", "376.260220 | \n", "376.214306 | \n", "0.045914 | \n", "-0.007302 | \n", "0.106432 | \n", "
2014-12-02 | \n", "375.23 | \n", "382.86 | \n", "378.39 | \n", "379.25 | \n", "6832.53 | \n", "2593576.46 | \n", "377.072532 | \n", "376.918296 | \n", "0.154236 | \n", "0.040752 | \n", "0.226969 | \n", "
\\n\"+\n", " \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n", " \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n", " \"
\\n\"+\n", " \"\\n\"+\n",
" \"from bokeh.resources import INLINE\\n\"+\n",
" \"output_notebook(resources=INLINE)\\n\"+\n",
" \"
\\n\"+\n",
" \"