Following my blog post Download & Play with Cryptocurrencies Historical Data in Python, I got several times questions on how to get the historical data. In the previous blog, I used a Python wrapper of the CryptoCompare API for historical data. Actually, CryptoCompare provides several APIs, not only for historical data, crypto related news too! Since last time, they have updated the documentation of their APIs, it can be found there: CryptoCompare {APIs}.

Using the Python wrapper:

%load_ext autoreload
%autoreload 2

import operator
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from crycompare import Price, History

%matplotlib inline
API_REQUEST = False
if API_REQUEST:
    p = Price()

    coinList = p.coinList()
    coins = sorted(list(coinList['Data'].keys()))
    
    h = History()

    df_dict = {}
    for idx_coin, coin in zip(range(len(coins)), coins):
        print(idx_coin, "/", len(coins), coin)
        histo = h.histoDay(coin,'USD',allData=True)
        if histo['Data']:
            df_histo = pd.DataFrame(histo['Data'])
            df_histo['time'] = pd.to_datetime(df_histo['time'], unit='s')
            df_histo.index = df_histo['time']
            del df_histo['time']

            df_dict[coin] = df_histo

    crypto_histo = pd.concat(df_dict.values(), axis=1, keys=df_dict.keys())
    crypto_histo.to_hdf('crypto_historical_data_20180514.h5',
                        key='crypto', mode='w', complevel=9)
else:
    crypto_histo = pd.read_hdf('crypto_historical_data_20180514.h5')
crypto_histo[['BTC','ETH']].tail()
BTC ETH
close high low open volumefrom volumeto close high low open volumefrom volumeto
time
2018-05-10 9032.22 9393.95 9017.13 9321.52 67915.99 6.298506e+08 723.61 767.22 722.65 751.27 383612.95 2.875370e+08
2018-05-11 8421.00 9032.27 8363.50 9032.22 134727.27 1.176889e+09 677.80 735.97 665.49 723.63 748412.09 5.188484e+08
2018-05-12 8486.67 8653.80 8225.97 8420.82 92805.87 7.839723e+08 683.64 690.88 638.47 677.80 487345.93 3.245909e+08
2018-05-13 8709.46 8773.96 8350.91 8488.07 61066.94 5.253959e+08 729.34 740.02 669.96 683.64 385804.48 2.740378e+08
2018-05-14 8772.82 8772.82 8312.72 8709.46 59306.44 5.055394e+08 733.11 739.89 685.75 729.34 479762.22 3.414134e+08

And the code for the wrapper, i.e. crycompare.py:

import sys
import requests
import warnings


class Price:
	def __init__(self):
		self.__coinlisturl = 'https://www.cryptocompare.com/api/data/coinlist/'
		self.__priceurl = 'https://min-api.cryptocompare.com/data/price?'
		self.__pricemultiurl = 'https://min-api.cryptocompare.com/data/pricemulti?'
		self.__pricemultifullurl = 'https://min-api.cryptocompare.com/data/pricemultifull?'
		self.__generateavgurl = 'https://min-api.cryptocompare.com/data/generateAvg?'
		self.__dayavgurl = 'https://min-api.cryptocompare.com/data/dayAvg?'
		self.__historicalurl = 'https://min-api.cryptocompare.com/data/pricehistorical?'
		self.__coinsnapshoturl = 'https://www.cryptocompare.com/api/data/coinsnapshot/?'
		self.__coinsnapshotfull = 'https://www.cryptocompare.com/api/data/coinsnapshotfullbyid/?'

	def coinList(self):
		return self.__get_url(self.__coinlisturl)

	def price(self, from_curr, to_curr, e=None, extraParams=None, sign=False, tryConversion=True):
		return self.__get_price(self.__priceurl, from_curr, to_curr, e, extraParams, sign, tryConversion)

	def priceMulti(self, from_curr, to_curr, e=None, extraParams=None, sign=False, tryConversion=True):
		return self.__get_price(self.__pricemultiurl, from_curr, to_curr, e, extraParams, sign, tryConversion)

	def priceMultiFull(self, from_curr, to_curr, e=None, extraParams=None, sign=False, tryConversion=True):
		return self.__get_price(self.__pricemultifullurl, from_curr, to_curr, e, extraParams, sign, tryConversion)

	def priceHistorical(self, from_curr, to_curr, markets, ts=None, e=None, extraParams=None,
						sign=False, tryConversion=True):
		return self.__get_price(self.__historicalurl, from_curr, to_curr, markets, e, extraParams, sign, tryConversion)

	def generateAvg(self, from_curr, to_curr, markets, extraParams=None, sign=False, tryConversion=True):
		return self.__get_avg(self.__generateavgurl, from_curr, to_curr, markets, extraParams, sign, tryConversion)

	def dayAvg(self, from_curr, to_curr, e=None, extraParams=None, sign=False, tryConversion=True,
			   avgType=None, UTCHourDiff=0, toTs=None):
		return self.__get_avg(self.__dayavgurl, from_curr, to_curr, e, extraParams, sign,
							tryConversion, avgType, UTCHourDiff, toTs)

	def coinSnapshot(self, from_curr, to_curr):
		return self.__get_url(self.__coinsnapshoturl + 'fsym=' + from_curr.upper() + '&tsym=' + to_curr.upper())

	def coinSnapshotFullById(self, coin_id):
		return self.__get_url(self.__coinsnapshotfull + 'id=' + str(coin_id))

	def __get_price(self, baseurl, from_curr, to_curr, e=None, extraParams=None, sign=False,
				  tryConversion=True, markets=None, ts=None):
		args = list()
		if isinstance(from_curr, str):
			args.append('fsym=' + from_curr.upper())
		elif isinstance(from_curr, list):
			args.append('fsyms=' + ','.join(from_curr).upper())
		if isinstance(to_curr, list):
			args.append('tsyms=' + ','.join(to_curr).upper())
		elif isinstance(to_curr, str):
			args.append('tsyms=' + to_curr.upper())
		if isinstance(markets, str):
			args.append('markets=' + markets)
		elif isinstance(markets, list):
			args.append('markets=' + ','.join(markets))
		if e:
			args.append('e=' + e)
		if extraParams:
			args.append('extraParams=' + extraParams)
		if sign:
			args.append('sign=true')
		if ts:
			args.append('ts=' + str(ts))
		if not tryConversion:
			args.append('tryConversion=false')
		if len(args) >= 2:
			return self.__get_url(baseurl + '&'.join(args))
		else:
			raise ValueError('Must have both fsym and tsym arguments.')

	def __get_avg(self, baseurl, from_curr, to_curr, markets=None, e=None, extraParams=None,
				sign=False, tryConversion=True, avgType=None, UTCHourDiff=0, toTs=None):
		args = list()
		if isinstance(from_curr, str):
			args.append('fsym=' + from_curr.upper())
		if isinstance(to_curr, str):
			args.append('tsym=' + to_curr.upper())
		if isinstance(markets, str):
			args.append('markets=' + markets)
		elif isinstance(markets, list):
			args.append('markets=' + ','.join(markets))
		if e:
			args.append('e=' + e)
		if extraParams:
			args.append('extraParams=' + extraParams)
		if sign:
			args.append('sign=true')
		if avgType:
			args.append('avgType=' + avgType)
		if UTCHourDiff:
			args.append('UTCHourDiff=' + str(UTCHourDiff))
		if toTs:
			args.append('toTs=' + toTs)
		if not tryConversion:
			args.append('tryConversion=false')
		if len(args) >= 2:
			return self.__get_url(baseurl + '&'.join(args))
		else:
			raise ValueError('Must have both fsym and tsym arguments.')

	def __get_url(self, url):
		raw_data = requests.get(url)
		raw_data.encoding = 'utf-8'
		if raw_data.status_code != 200:
			raw_data.raise_for_status()
			return False
		try:
			if isinstance(raw_data.text, unicode):
				warnings.warn('Object returned is of type unicode. Cannot parse to str in Python 2.')
		except NameError:
			pass
		return raw_data.json()


class History:
	def __init__(self):
		self.__histominuteurl = 'https://min-api.cryptocompare.com/data/histominute?'
		self.__histohoururl = 'https://min-api.cryptocompare.com/data/histohour?'
		self.__histodayurl = 'https://min-api.cryptocompare.com/data/histoday?'

	def histoMinute(self, from_curr, to_curr, e=None, extraParams=None,
					sign=False, tryConversion=True, aggregate=None, limit=None, toTs=None):
		return self.__get_price(self.__histominuteurl, from_curr, to_curr, e, extraParams, sign,
								tryConversion, aggregate, limit, toTs)

	def histoHour(self, from_curr, to_curr, e=None, extraParams=None,
				  sign=False, tryConversion=True, aggregate=None, limit=None, toTs=None):
		return self.__get_price(self.__histohoururl, from_curr, to_curr, e, extraParams, sign,
								tryConversion, aggregate, limit, toTs)

	def histoDay(self, from_curr, to_curr, e=None, extraParams=None, sign=False,
				 tryConversion=True, aggregate=None, limit=None, toTs=None, allData=False):
		return self.__get_price(self.__histodayurl, from_curr, to_curr, e, extraParams, sign,
								tryConversion, aggregate, limit, toTs, allData)

	def __get_price(self, baseurl, from_curr, to_curr, e=None, extraParams=None, sign=False,
					tryConversion=True, aggregate=None, limit=None, toTs=None, allData=False):
		args = list()
		if isinstance(from_curr, str):
			args.append('fsym=' + from_curr.upper())
		if isinstance(to_curr, str):
			args.append('tsym=' + to_curr.upper())
		if e:
			args.append('e=' + e)
		if extraParams:
			args.append('extraParams=' + extraParams)
		if sign:
			args.append('sign=true')
		if aggregate:
			args.append('aggregate=' + str(aggregate))
		if limit:
			args.append('limit=' + str(limit))
		if toTs:
			args.append('toTs=' + str(toTs))
		if allData:
			args.append('allData=true')
		if not tryConversion:
			args.append('tryConversion=false')
		if len(args) >= 2:
			return self.__get_url(baseurl + '&'.join(args))
		else:
			raise ValueError('Must have both fsym and tsym arguments.')

	def __get_url(self, url):
		raw_data = requests.get(url)
		raw_data.encoding = 'utf-8'
		if raw_data.status_code != 200:
			raw_data.raise_for_status()
			return False
		try:
			if isinstance(raw_data.text, unicode):
				warnings.warn('Object returned is of type unicode. Cannot parse to str in Python 2.')
		except NameError:
			pass
		return raw_data.json()