QuantBrasil

Baixando Dados de Bitcoin, Ethereum e Outras Criptomoedas com Python

Rafael Quintanilha
Por Rafael Quintanilha
01 junho, 2022
Compartilhar:

Queridas por muitos, odiadas por outros, as criptomoedas dividem opiniões. O que é inegável, no entanto, é que pela sua natureza mais recente e tecnológica, é consideravelmente mais simples acessar uma base de dados de criptomoedas do que é tipicamente com os ativos brasileiros.

No QuantBrasil nós já ensinamos a criar sua própria base de dados de ações e até a exportar valores atualizados do ProfitChart. Hoje nós continuaremos nossa saga à procura da base de dados perfeita e aprenderemos como acessar dados de preços de criptos.

Se tudo que você está atrás, porém, é baixar os preços para executar estratégias, nós já lhe poupamos esse trabalho. No Simulador de Estratégias do QuantBrasil você pode realizar backtests em várias criptomoedas, das mais famosas como Bitcoin e Ethereum, até as mais voláteis como Dogecoin.

No entanto, se você é partidário do Do It Yourself, sem problemas. Mãos à obra!

Escolhendo a API

Talvez a parte mais sensível desse projeto é escolher qual API utilizar. API, do inglês Application Programming Interface, é uma forma que corretoras e outros serviços disponibilizam para que você se comunique com eles. Existem diversas por aí:

Não é objetivo desse post fazer um apanhado sobre pontos fortes e fracos de cada uma delas e é capaz que todas (e outras não mencionadas) suportem o que pretendemos fazer hoje de forma simples. No entanto, a Coinbase lançou recentemente a Coinbase Cloud, com diversos serviços de suporte para produtos de cripto e decidi utilizá-la.

Note que a API da Coinbase possui endpoints que podem ser classificados como públicos ou privados. Os endpoints públicos são aqueles de conhecimento geral, por exemplo os dados históricos do Ethereum ou o book de negociações do Bitcoin. Já os privados contém dados relativos a um usuário específico: por exemplo, todos os seus trades executados no mês de Abril.

Como estamos interessados apenas em dados públicos, nosso código ficará ainda mais simples. Fosse o caso de querermos dados privados, precisaríamos de uma API key.

Vamos ao que interessa então e aprender a baixar os dados históricos de criptomoedas.

Baixando dados do Bitcoin

A API da Coinbase é bastante robusta e eu convido você a explorá-la. Para fins de dados históricos, porém, estamos interessados apenas na área de Exchange.

Existem duas formas de se acessar os dados: através de uma API REST ou utilizando websockets.

REST API vs WebSockets

Se você é mais familiarizado com desenvolvimento de software, já deve ter se deparado com esses termos anteriormente. Se não, não tem problema! Vamos explicar rapidamente a diferença entre esses dois protocolos de modo que você possa fazer a melhor decisão para o seu caso de uso.

Uma API REST é a forma mais tradicional de se comunicar com um serviço. Nela, o provedor expõe uma URL, também chamada de endpoint, onde o consumidor (ou cliente) faz uma requisição HTTP utilizando parâmetros previamente acordados (por exemplo, na documentação da API).

Tecnicamente falando, uma API REST deve seguir alguns princípios, como ser idempotente. Sem entrar em muitos detalhes técnicos, esse tipo de comunicação é tradicional no sentido que utiliza-se o padrão cliente - servidor. Ou seja, o cliente faz uma requisição para o servidor, espera uma resposta, e fecha a comunicação. Se dali a pouco o dado mudou (como é o caso com APIs de ativos financeiros), outra requisição tem que ser feita.

Já na abordagem via WebSockets o cliente abre uma conexão com o servidor, mas ela não necessariamente é fechada. Ou seja, abre-se uma espécie de "pipeline" de dados que são atualizados em tempo real sem que o cliente precise solicitar os dados mais recentes.

É como se você comparasse ter uma cascata de água natural na sua casa (WebSockets) versus ter que ir na cozinha apertar o botão do filtro (API REST).

Qual é melhor? Como sempre na computação, depende do seu caso de uso. Você precisa de uma fonte de água natural na sua casa? Provavelmente não. A decisão entre API REST vs WebSockets depende de quão frequente você deseja que os dados sejam atualizados.

Por exemplo, se você quer o dado da última negociação do Bitcoin em real time, então WebSockets serão uma melhor solução. Por outro lado, se sua aplicação permite um delay de 15 minutos, então é mais que suficiente fazer uma requisição nesse período utilizando a API REST.

No nosso exemplo de hoje, não estamos tão preocupados em termos dados em real time, portanto utilizaremos a API REST.

Descobrindo o product id

Agora que decidimos qual serviço utilizaremos, podemos nos aprofundar na API da Coinbase.

De forma simplificada, todos os pares presentes na plataforma são chamados de produtos, e consequentemente possuem um product id. Esse id será utilizado para recuperar os dados históricos mais pra frente.

Existe um endpoint específico para retornar todos os trading pairs, e eu convido você a explorá-lo. Uma outra forma de vê-los é ir na página de Market Information da Coinbase e ver alguns dos pares mais famosos, além de outros detalhes.

Sendo assim, o product id do Bitcoin é BTC-USD, do Ethereum é ETH-USD, da Dogecoin é DOGE-USD, etc. Anote esse id sempre que quiser acessar a base de preços.

Baixando candles

Já falei demais: agora checou a hora de escrever algum código. Utilizaremos o endpoint de product candles para recuperar os dados de um par.

Antes, porém, precisamos descobrir quais são os parâmetros do endpoint, ou seja, quais dados precisamos enviar para a Coinbase para recuperar exatamente os dados que queremos.

Além do product id que já vimos, a API suporta os parâmetros start, end e granularity.

Os dois primeiros são óbvios: eles representam o intervalo que queremos buscar os dados.

Já a granularidade nada mais é do que o timeframe do dado. Uma granularity de 900 representa 900 segundos, ou 15 minutos (M15). Já 86400 equivaleria ao timeframe diário (D1).

Atente que nem todas as granularidades são suportadas. Por exemplo, o valor de 7200 (ou 120 minutos) não é aceito. Há uma saída, porém: no final desse tutorial nós aprenderemos a utilizar a função resample para gerar timeframes customizados.

A tabela a seguir representam as granularidades aceitas pela Coinbase no momento da escrita deste post:

GranularidadePeríodoTimeframe
601 minutoM1
3005 minutosM5
90015 minutosM15
36001 horaH1
216006 horasH6
864001 diaD1

Importando as bibliotecas necessárias

Agora que já sabemos todos os parâmetros necesśarios, vamos realizar nosso primeiro teste e baixar os dados de preço diários do Bitcoin no 1º trimestre de 2022.

Para isso, vamos importar a biblioteca requests do Python e realizar a nossa primeira requisição HTTP.

import requests

# Mount URL
product_id = 'BTC-USD'
url = f"https://api.exchange.coinbase.com/products/{product_id}/candles"

# Define parameters
granularity = 86400
start_date = "2022-01-01T00:00:00"
end_date = "2022-03-31T23:59:59"

# Make sure to pass dates as ISO
params = {
    "start": start_date,
    "end": end_date,
    "granularity": granularity
}

# Define request headers
headers = {"content-type": "application/json"}

data = requests.get(url, params=params, headers=headers)
data

Algumas coisas aconteceram aqui. Primeiro, construíamos a URL final que receberá nossa requisição (lembra dos endpoints?). Depois, ajustamos a granularidade para 86400 (ou seja, 1 dia de acordo com a nossa tabela) e definimos as datas como sendo o primeiro trimestre de 2022 no formato ISO 8601.

Importante: a API da Coinbase trabalha com datas em UTC, ou seja, 3 horas a mais que o horário de Brasília.

Fazemos uma requisição simples passando nossos parâmetros e definindo o content-type como JSON e recebemos um HTTP Status Code 200, ou seja, sucesso! Nossos dados foram baixados.

Ainda assim, esse não é o formato mais útil de todos. Em muitos casos, vamos querer analisar esses dados como um JSON ou, já que estamos no Python, como um DataFrame. Vamos então transformar os dados em JSON:

data = data.json()
data[:5]
[[1648684800, 45211, 47624.09, 47078.02, 45528.45, 17454.79144251], [1648598400, 46544.89, 47717.01, 47448.41, 47078.03, 12672.44584707], [1648512000, 46589, 48124.94, 47146.92, 47454.19, 15776.65108647], [1648425600, 46662.28, 48240, 46850.01, 47144.92, 18787.66318707], [1648339200, 44437.22, 46950, 44538.21, 46850.01, 10356.10317002]]

Agora sim. Recebemos uma lista de listas (exibidas apenas as 5 primeiras), onde os dados obedecem a ordem: timestamp, low, high, open, close, volume. Note que a ordem é diferente do formato OHLCV que costumamos ver por aí!

Finalmente, vamos transformar nossa lista de listas em uma DataFrame respeitando a ordem dos elementos nas listas:

import pandas as pd

columns = ['timestamp', "low", "high", "open", "close", "volume"]

# Create a dataframe with results
df = pd.DataFrame(data=data, columns=columns)

# Create datetime column, set it as index and drop timestamp column
df['datetime'] = pd.to_datetime(df['timestamp'], unit='s')
df.set_index('datetime', inplace=True)
df.drop('timestamp', axis=1, inplace=True)

df
lowhighopenclosevolume
datetime
2022-03-3145211.0047624.0947078.0245528.4517454.791443
2022-03-3046544.8947717.0147448.4147078.0312672.445847
2022-03-2946589.0048124.9447146.9247454.1915776.651086
2022-03-2846662.2848240.0046850.0147144.9218787.663187
2022-03-2744437.2246950.0044538.2146850.0110356.103170
..................
2022-01-0542500.0047076.5545817.1343436.0425067.674768
2022-01-0445515.0047532.8946459.5745814.6116320.327129
2022-01-0345700.0047583.3347299.0646459.5610841.810205
2022-01-0246633.3647990.0047733.4347299.076833.498455
2022-01-0146205.0047967.1246211.2447733.439463.661711

90 rows × 5 columns

E pronto! Repare que criamos uma nova coluna datetime onde trabalhamos com a data num formato mais amigável, transformando-a no índice no DataFrame. Por fim, como a coluna timestamp não nos será mais necessária, a deletamos.

Você já pode aproveitar o poder dos DataFrames com seus dados de cripto!

max_value = df['high'].max()
print(f"Max BTC price between {start_date[:10]} and {end_date[:10]} was ${max_value}")
Max BTC price between 2022-01-01 and 2022-03-31 was $48240.0

Baixando dados de períodos maiores

Nosso código funcionou perfeitamente, mas ainda não terminamos nossa exploração. Para facilitar daqui pra frente, vamos encapsular o que fizemos até agora em uma função get_crypto_data:

def get_crypto_data(product_id, start_date, end_date, granularity):
  # Mount URL
  url = f"https://api.exchange.coinbase.com/products/{product_id}/candles"

  # Make sure to pass dates as ISO
  params = {
      "start": start_date,
      "end": end_date,
      "granularity": granularity
  }

  # Define request headers
  headers = {"content-type": "application/json"}

  data = requests.get(url, params=params, headers=headers)
  data = data.json()
  
  columns = ['timestamp', "low", "high", "open", "close", "volume"]

  # Create a dataframe with results
  df = pd.DataFrame(data=data, columns=columns)

  # Create datetime column, set it as index and drop timestamp column
  df['datetime'] = pd.to_datetime(df['timestamp'], unit='s')
  df.set_index('datetime', inplace=True)
  df.drop('timestamp', axis=1, inplace=True)

  return df

Nossa função recebe todos os parâmetros necessários para baixar os dados. Sendo assim, vamos testá-la utilizando uma granularidade de 900, ou seja, 15 minutos:

df = get_crypto_data(
  product_id="BTC-USD",
  start_date="2022-01-01T00:00:00",
  end_date="2022-03-31T23:59:59",
  granularity=900
)

df
lowhighopenclosevolume
datetime

Ora, o DataFrame está vazio! Isso acontece pois a Coinbase limita a quantidade de data points retornados por requisição. Isso faz sentido, caso contrário algum usuário poderia solicitar todos os dados no timeframe de 1 minuto a cada segundo, fritando os servidores da empresa.

Segundo a documentação, o máximo de data points são 300 candles. Vamos reajustar nossos parâmetros de data para confirmar:

df = get_crypto_data(
  product_id="BTC-USD",
  start_date="2022-03-29T00:00:00",
  end_date="2022-03-31T23:59:59",
  granularity=900
)

df
lowhighopenclosevolume
datetime
2022-03-31 23:45:0045211.0045586.9745447.8145528.45331.390427
2022-03-31 23:30:0045380.0045746.5145696.3245447.30167.929201
2022-03-31 23:15:0045563.3345715.2545698.9745696.32164.113664
2022-03-31 23:00:0045669.2645819.9845806.5345700.78102.271951
2022-03-31 22:45:0045707.3845845.0945707.7945809.8971.263482
..................
2022-03-29 01:00:0047356.3647470.9947414.8247445.3973.969876
2022-03-29 00:45:0047355.6047630.7147425.1247411.67154.791100
2022-03-29 00:30:0047350.8447445.4347357.6447425.0893.277072
2022-03-29 00:15:0047252.0447519.0947297.0447357.62205.391625
2022-03-29 00:00:0047048.5547356.0547146.9247297.04315.522117

288 rows × 5 columns

Agora sim. Selecionando um período de 3 dias com a granularidade ajustada para 15 minutos, recuperamos 288 data points, praticamente no limite permitido por requisição.

Já andamos grande parte do caminho mas ainda não chegamos ao fim. 300 candles não fazem um verão, e portanto precisamos de um jeito de buscar quantos dados forem necessários, independente da limitação.

Vejamos o que a documentação nos sugere:

If you wish to retrieve fine granularity data over a larger time range, you will need to make multiple requests with new start/end ranges.

Trocando em miúdos: para recuperar mais dados que o limite precisamos iterar sobre o intervalo desejado e "quebrá-lo" em pedaços de 300 em 300 candles. Vamos resolver esse problema.

Iterando sobre o intervalo de tempo

Vamos retomar nosso intervalo original: o 1º trimestre de 2022. Conseguimos baixar os dados no timeframe diário, uma vez que existem menos de 300 pontos nessas configurações. Mas não fomos capazes de receber os dados no timeframe de 15 minutos por termos estourado o limite de 300 data points por requisição.

Para bypassar essa regra, criaremos o seguinte algoritmo:

  1. Inicializamos no início do intervalo;
  2. Incrementamos o intervalo com o número máximo de candles permitidos para a granularidade em questão;
  3. Baixamos os dados nesse subintervalo;
  4. Se o final do subintervalo for menor que o final do intervalo original, repetimos o passo 1;
  5. Ao final do loop, concatenamos os dados baixados em cada iteração.

Por exemplo, se o início do intervalo é 2022-01-01T00:00:00 (ou seja, meia noite do dia 01/01/2022 em UTC) e granularidade é 900 (ou seja, 15 minutos), sabemos que temos 4 blocos de 15 minutos por hora e 24 horas, sendo assim 96 candles de 15 minutos em um dia.

Em 3 dias, temos 288 candles (não é coincidência que nosso dataframe anterior tenha exatamente esse número de linhas).

Como sobram ainda 12 candles para o limite de 300, e cada candle tem 15 minutos, então ainda podemos "andar" mais 180 minutos (ou 3 horas). Sendo assim, poderíamos escrever:

df = get_crypto_data(
  product_id="BTC-USD",
  start_date="2022-01-01T00:00:00",
  end_date="2022-01-04T03:00:00",
  granularity=900
)

df
lowhighopenclosevolume
datetime
2022-01-04 03:00:0046023.0846144.9246102.5846041.9494.959678
2022-01-04 02:45:0046088.5746238.2446201.3846104.6997.587071
2022-01-04 02:30:0046077.1546263.4646130.4146201.3891.763870
2022-01-04 02:15:0045950.0046155.2045984.1746132.1885.814546
2022-01-04 02:00:0045951.4646149.4346060.3745985.43132.597476
..................
2022-01-01 01:15:0046736.8146931.0046762.5146800.00231.691797
2022-01-01 01:00:0046573.6546762.5146656.8546760.66133.183740
2022-01-01 00:45:0046566.1746744.9946603.7146656.85165.301353
2022-01-01 00:30:0046334.0846664.9146359.1046603.64151.375780
2022-01-01 00:15:0046211.3546394.4446315.4646359.09188.236630

300 rows × 5 columns

Embora o DataFrame tenha exatos 300 pontos, note que estamos "deslocados" de 1 ponto, uma vez que a contagem está começando em 00:15, e não às 00:00 como seria esperado. Podemos corrigir subtraindo 1 segundo das nossas datas inicial e final:

df = get_crypto_data(
  product_id="BTC-USD",
  start_date="2021-12-31T23:59:59",
  end_date="2022-01-04T02:59:59",
  granularity=900
)

df
lowhighopenclosevolume
datetime
2022-01-04 02:45:0046088.5746238.2446201.3846104.6997.587071
2022-01-04 02:30:0046077.1546263.4646130.4146201.3891.763870
2022-01-04 02:15:0045950.0046155.2045984.1746132.1885.814546
2022-01-04 02:00:0045951.4646149.4346060.3745985.43132.597476
2022-01-04 01:45:0046044.3146205.0546132.8046060.3898.783973
..................
2022-01-01 01:00:0046573.6546762.5146656.8546760.66133.183740
2022-01-01 00:45:0046566.1746744.9946603.7146656.85165.301353
2022-01-01 00:30:0046334.0846664.9146359.1046603.64151.375780
2022-01-01 00:15:0046211.3546394.4446315.4646359.09188.236630
2022-01-01 00:00:0046205.0046500.0046211.2446316.17317.402547

300 rows × 5 columns

Agora sim! Nossa requisição retorna exatamente os 300 primeiros data points de 2022 no timeframe de 15 minutos.

Como cada intervalo comporta 3 dias e 3 horas de dados, podemos utilizar o final do subintervalo como início do próximo, e "andar" mais 3 dias e 3 horas no final do subintervalo:

df = get_crypto_data(
  product_id="BTC-USD",
  start_date="2022-01-04T02:59:59",
  end_date="2022-01-07T05:59:59",
  granularity=900
)

df
lowhighopenclosevolume
datetime
2022-01-07 05:45:0041615.8141850.0041711.2041834.30290.354053
2022-01-07 05:30:0041642.2141945.6541791.3641712.43459.702018
2022-01-07 05:15:0041704.8241893.8541713.9741788.88284.561626
2022-01-07 05:00:0041522.3141746.5441691.6341718.69282.162749
2022-01-07 04:45:0041404.0041772.3141416.3141689.99575.720724
..................
2022-01-04 04:00:0046181.0946398.4146319.9946248.7885.736188
2022-01-04 03:45:0046300.0046385.2046342.6046319.8488.525080
2022-01-04 03:30:0046180.2346342.6046192.3346342.6098.737794
2022-01-04 03:15:0046015.8946211.7546041.8546192.33106.962232
2022-01-04 03:00:0046023.0846144.9246102.5846041.9494.959678

300 rows × 5 columns

E aí está. Recomeçamos exatamente de onde paramos e andamos os 300 candles permitidos.

Agora que você já entendeu o algoritmo podemos alterar get_crypto_data para fazer isso automaticamente:

from datetime import datetime, timedelta

def get_crypto_data(product_id, start_date, end_date, granularity):
  # Mount URL
  url = f"https://api.exchange.coinbase.com/products/{product_id}/candles"

  # Delta is the increment in time from one candle to another
  delta = timedelta(seconds=granularity)

  # Coinbase's limitation
  max_candles = 300

  # Start from the last second of last day to prevent duplications
  current_date = datetime.strptime(start_date, '%Y-%m-%dT%H:%M:%S') - timedelta(seconds=1)
  end_date = datetime.strptime(end_date, '%Y-%m-%dT%H:%M:%S') - timedelta(seconds=1)

  # We'll add results in each iteration to this list
  results = []

  # Iterate over interval
  while current_date < end_date:
    # Get next date. It's the current date + increment if still within the interval,
    # else it's the end date
    next_date = current_date + max_candles * delta
    next_date = next_date if next_date < end_date else end_date

    # Make sure to pass dates as ISO
    params = {
        "start": current_date.isoformat(),
        "end": next_date.isoformat(),
        "granularity": granularity
    }

    headers = {"content-type": "application/json"}

    data = requests.get(url, params=params, headers=headers)
    data = data.json()

    # Make sure to add more recent data to the top of the list
    results = data + results

    # Update cursor
    current_date = next_date
  
  columns = ['timestamp', "low", "high", "open", "close", "volume"]

  # Create a dataframe with results
  df = pd.DataFrame(data=results, columns=columns)

  # Create datetime column, set it as index and drop timestamp column
  df['datetime'] = pd.to_datetime(df['timestamp'], unit='s')
  df.set_index('datetime', inplace=True)
  df.drop('timestamp', axis=1, inplace=True)

  return df

Algumas coisas importantes aqui:

  • Utilizamos a função timedelta para manipular a data, subtraindo 1 segundo dos intervalos;
  • Definimos max_candles como o limite de data points em cada iteração;
  • Definimos next_date como o final do subintervalo;
  • Baixamos os dados e os adicionamos a uma lista;
  • No final, convertermos a lista para DataFrame.

Chegou a hora de testar nossa intenção original: baixar todos os candles de 15 minutos do Bitcoin no 1º trimestre de 2022.

df = get_crypto_data(
  product_id="BTC-USD",
  start_date="2022-01-01T00:00:00",
  end_date="2022-03-31T23:59:59",
  granularity=900
)

df
lowhighopenclosevolume
datetime
2022-03-31 23:45:0045211.0045586.9745447.8145528.45331.390427
2022-03-31 23:30:0045380.0045746.5145696.3245447.30167.929201
2022-03-31 23:15:0045563.3345715.2545698.9745696.32164.113664
2022-03-31 23:00:0045669.2645819.9845806.5345700.78102.271951
2022-03-31 22:45:0045707.3845845.0945707.7945809.8971.263482
..................
2022-01-01 01:00:0046573.6546762.5146656.8546760.66133.183740
2022-01-01 00:45:0046566.1746744.9946603.7146656.85165.301353
2022-01-01 00:30:0046334.0846664.9146359.1046603.64151.375780
2022-01-01 00:15:0046211.3546394.4446315.4646359.09188.236630
2022-01-01 00:00:0046205.0046500.0046211.2446316.17317.402547

8640 rows × 5 columns

E aí está. Nos 90 dias do 1º trimestre existem 90 * 96 = 8640 candles, justamente o tamanho do nosso DataFrame. Vitória!

Baixando dados em outros timeframes

Nossa última missão do dia é descobrir como fazer para utilizar granularidades customizadas. Ora, pode ser que o seu timeframe favorito não esteja na lista!

Felizmente existe a poderosíssima função resample no pandas. Suponhamos que nosso objetivo é baixar todos os dados de Ethereum no 1º trimestre de 2022, porém no timeframe de 120 minutos.

No entanto, a granularidade suportada mais próxima disso é 3600, ou 1 hora. Vamos começar então baixando os dados no timeframe de granularidade mais próxima:

df = get_crypto_data(
  product_id="ETH-USD",
  start_date="2022-01-01T00:00:00",
  end_date="2022-03-31T23:59:59",
  granularity=3600
)

df
lowhighopenclosevolume
datetime
2022-03-31 23:00:003263.423298.723291.513283.085846.861734
2022-03-31 22:00:003274.343294.203288.293291.305073.242391
2022-03-31 21:00:003288.103307.393300.573288.276042.383361
2022-03-31 20:00:003280.003305.003284.273300.589915.034122
2022-03-31 19:00:003283.323312.903309.733284.1711106.793949
..................
2022-01-01 04:00:003705.093727.343723.013706.876879.855088
2022-01-01 03:00:003721.993736.083728.013722.963711.649049
2022-01-01 02:00:003722.013737.383724.813727.945135.417450
2022-01-01 01:00:003709.153748.113722.793724.229223.416393
2022-01-01 00:00:003675.443728.873675.813722.788114.958722

2160 rows × 5 columns

Nossa função retornou os dados corretamente, porém precisamos agrupá-los de forma que se encaixe no nosso timeframe. Como queremos combiná-los em intervalos de 2 horas, precisamos agregar cada 2 linhas em uma nova, utilizando as seguintes regras:

  • Queremos o menor valor na coluna low;
  • Queremos o maior valor na coluna high;
  • Queremos o primeiro valor na coluna open;
  • Queremos o último valor na coluna close;
  • Queremos a soma dos valores na coluna volume.

Por exemplo, os dois primeiros candles do DataFrame (2022-01-01 00:00:00 e 2022-01-01 01:00:00) devem ser agregados em um candle cujo datetime é 2022-01-01 00:00:00, low seja 3675.44, high seja 3748.11, open seja 3675.81, close seja 3724.22 e _volume seja 17338.36.

df = df.resample('120 min').agg({
  "low": "min",
  "high": "max",
  "open": "first",
  "close": "last",
  "volume": "sum"
})

df
lowhighopenclosevolume
datetime
2022-01-01 00:00:003675.443748.113675.813724.2217338.375115
2022-01-01 02:00:003721.993737.383724.813722.968847.066499
2022-01-01 04:00:003702.433763.833723.013735.0012822.868144
2022-01-01 06:00:003700.003742.873735.013714.2713458.744710
2022-01-01 08:00:003708.253733.473714.883718.443064.374500
..................
2022-03-31 14:00:003336.363389.153381.403351.1426798.403425
2022-03-31 16:00:003265.533354.963350.973296.0038338.470086
2022-03-31 18:00:003260.993312.903295.713284.1723085.379080
2022-03-31 20:00:003280.003307.393284.273288.2715957.417484
2022-03-31 22:00:003263.423298.723288.293283.0810920.104125

1080 rows × 5 columns

Aposto que você não acreditava que seria tão simples. Sim, passando a string '120 min' o pandas é esperto o suficiente para reagrupar as linhas utilizando as funções específicas para cada coluna. Note como o novo DataFrame é metade do original. Elegante.

Conclusão

Graças à natureza tecnológica das criptomoedas existem diversas APIs públicas com seus dados históricos. A Coinbase, uma das maiores exchanges americanas e que é listada na NASDAQ, disponibiliza sua API através da Coinbase Cloud.

Aprendemos que a Coinbase limita em 300 dados por requisição. Sendo assim, devemos fazer múltiplas requisições iterativamente se queremos baixar mais dados que isso.

Por fim, nem todos os timeframes são aceitos. Apenas certas granularidades são permitidas, como M15, H1 e D1. No entanto, utilizando o poderoso resample podemos agregar candles de acordo com regras específicas.

Se você tem interesse em estratégias quantitativas para criptomoedas, considere criar sua conta no QuantBrasil. No QuantBrasil oferecemos o Simulador de Estratégias, onde você pode realizar backtests em criptomoedas e outros ativos de forma simples e rápida.

Sendo membro Premium você desbloqueia todas as estratégias, ativos e timeframes no nosso simulador, além de outras ferramentas imprescindíveis para investidores e traders. Confira nossos planos!

Até a próxima!