Files
OpenBB/openbb_terminal/cryptocurrency/due_diligence/glassnode_model.py
montezdesousa bfc4c021e7 Fix incorrect date typing in signatures (#3524)
* Report fixes

* replace wrong typing

* mypy

* mypy again

Co-authored-by: james <jmaslek11@gmail.com>
2022-11-22 17:15:40 -05:00

616 lines
15 KiB
Python

from datetime import datetime, timedelta
import logging
import json
from typing import Optional
import pandas as pd
import requests
from openbb_terminal import config_terminal as cfg
from openbb_terminal.decorators import log_start_end, check_api_key
from openbb_terminal.rich_config import console
from openbb_terminal.helper_funcs import str_date_to_timestamp
# pylint: disable=unsupported-assignment-operation
logger = logging.getLogger(__name__)
# pylint: disable=unsupported-assignment-operation
api_url = "https://api.glassnode.com/v1/metrics/"
GLASSNODE_SUPPORTED_HASHRATE_ASSETS = ["BTC", "ETH"]
GLASSNODE_SUPPORTED_EXCHANGES = [
"aggregated",
"binance",
"bittrex",
"coinex",
"gate.io",
"gemini",
"huobi",
"kucoin",
"poloniex",
"bibox",
"bigone",
"bitfinex",
"hitbtc",
"kraken",
"okex",
"bithumb",
"zb.com",
"cobinhood",
"bitmex",
"bitstamp",
"coinbase",
"coincheck",
"luno",
]
GLASSNODE_SUPPORTED_ASSETS = [
"BTC",
"ETH",
"LTC",
"AAVE",
"ABT",
"AMPL",
"ANT",
"ARMOR",
"BADGER",
"BAL",
"BAND",
"BAT",
"BIX",
"BNT",
"BOND",
"BRD",
"BUSD",
"BZRX",
"CELR",
"CHSB",
"CND",
"COMP",
"CREAM",
"CRO",
"CRV",
"CVC",
"CVP",
"DAI",
"DDX",
"DENT",
"DGX",
"DHT",
"DMG",
"DODO",
"DOUGH",
"DRGN",
"ELF",
"ENG",
"ENJ",
"EURS",
"FET",
"FTT",
"FUN",
"GNO",
"GUSD",
"HEGIC",
"HOT",
"HPT",
"HT",
"HUSD",
"INDEX",
"KCS",
"LAMB",
"LBA",
"LDO",
"LEO",
"LINK",
"LOOM",
"LRC",
"MANA",
"MATIC",
"MCB",
"MCO",
"MFT",
"MIR",
"MKR",
"MLN",
"MTA",
"MTL",
"MX",
"NDX",
"NEXO",
"NFTX",
"NMR",
"Nsure",
"OCEAN",
"OKB",
"OMG",
"PAX",
"PAY",
"PERP",
"PICKLE",
"PNK",
"PNT",
"POLY",
"POWR",
"PPT",
"QASH",
"QKC",
"QNT",
"RDN",
"REN",
"REP",
"RLC",
"ROOK",
"RPL",
"RSR",
"SAI",
"SAN",
"SNT",
"SNX",
"STAKE",
"STORJ",
"sUSD",
"SUSHI",
"TEL",
"TOP",
"UBT",
"UMA",
"UNI",
"USDC",
"USDK",
"USDT",
"UTK",
"VERI",
"WaBi",
"WAX",
"WBTC",
"WETH",
"wNMX",
"WTC",
"YAM",
"YFI",
"ZRX",
]
INTERVALS_HASHRATE = ["24h", "1w", "1month"]
INTERVALS_ACTIVE_ADDRESSES = ["24h", "1w", "1month"]
@log_start_end(log=logger)
@check_api_key(["API_GLASSNODE_KEY"])
def get_close_price(
symbol: str,
start_date: str = "2010-01-01",
end_date: Optional[str] = None,
print_errors: bool = True,
) -> pd.DataFrame:
"""Returns the price of a cryptocurrency
[Source: https://glassnode.com]
Parameters
----------
symbol : str
Crypto to check close price (BTC or ETH)
start_date : str
Initial date, format YYYY-MM-DD
end_date : str
Final date, format YYYY-MM-DD
print_errors: bool
Flag to print errors. Default: True
Returns
-------
pd.DataFrame
price over time
"""
if end_date is None:
end_date = datetime.now().strftime("%Y-%m-%d")
ts_start_date = str_date_to_timestamp(start_date)
ts_end_date = str_date_to_timestamp(end_date)
url = api_url + "market/price_usd_close"
parameters = {
"api_key": cfg.API_GLASSNODE_KEY,
"a": symbol,
"i": "24h",
"s": str(ts_start_date),
"u": str(ts_end_date),
}
r = requests.get(url, params=parameters)
df = pd.DataFrame()
if r.status_code == 200:
df = pd.DataFrame(json.loads(r.text))
if df.empty:
if print_errors:
console.print(f"No data found for {symbol} price.\n")
else:
df = df.set_index("t")
df.index = pd.to_datetime(df.index, unit="s")
elif r.status_code == 401:
if print_errors:
console.print("[red]Invalid API Key[/red]\n")
else:
if print_errors:
console.print(r.text)
return df
@log_start_end(log=logger)
@check_api_key(["API_GLASSNODE_KEY"])
def get_non_zero_addresses(
symbol: str,
start_date: str = "2010-01-01",
end_date: Optional[str] = None,
) -> pd.DataFrame:
"""Returns addresses with non-zero balance of a certain symbol
[Source: https://glassnode.com]
Parameters
----------
symbol : str
Asset to search (e.g., BTC)
start_date : str
Initial date, format YYYY-MM-DD
end_date : str
Final date, format YYYY-MM-DD
Returns
-------
pd.DataFrame
addresses with non-zero balances
"""
if end_date is None:
end_date = datetime.now().strftime("%Y-%m-%d")
ts_start_date = str_date_to_timestamp(start_date)
ts_end_date = str_date_to_timestamp(end_date)
url = api_url + "addresses/non_zero_count"
parameters = {
"api_key": cfg.API_GLASSNODE_KEY,
"a": symbol,
"i": "24h",
"s": str(ts_start_date),
"u": str(ts_end_date),
}
r = requests.get(url, params=parameters)
df = pd.DataFrame()
if r.status_code == 200:
df = pd.DataFrame(json.loads(r.text))
if df.empty:
console.print(f"No data found for {symbol}'s non-zero addresses.\n")
else:
df["t"] = pd.to_datetime(df["t"], unit="s")
df = df.set_index("t")
elif r.status_code == 401:
console.print("[red]Invalid API Key[/red]\n")
else:
console.print(r.text)
return df
@log_start_end(log=logger)
@check_api_key(["API_GLASSNODE_KEY"])
def get_active_addresses(
symbol: str,
interval: str = "24h",
start_date: str = "2010-01-01",
end_date: Optional[str] = None,
) -> pd.DataFrame:
"""Returns active addresses of a certain symbol
[Source: https://glassnode.com]
Parameters
----------
symbol : str
Asset to search active addresses (e.g., BTC)
interval : str
Interval frequency (e.g., 24h)
start_date : str
Initial date, format YYYY-MM-DD
end_date : str
Final date, format YYYY-MM-DD
Returns
-------
pd.DataFrame
active addresses over time
"""
if end_date is None:
end_date = datetime.now().strftime("%Y-%m-%d")
ts_start_date = str_date_to_timestamp(start_date)
ts_end_date = str_date_to_timestamp(end_date)
url = api_url + "addresses/active_count"
parameters = {
"api_key": cfg.API_GLASSNODE_KEY,
"a": symbol,
"i": interval,
"s": str(ts_start_date),
"u": str(ts_end_date),
}
r = requests.get(url, params=parameters)
df = pd.DataFrame()
if r.status_code == 200:
df = pd.DataFrame(json.loads(r.text))
if df.empty:
console.print(f"No data found for {symbol}'s active addresses.\n")
else:
df["t"] = pd.to_datetime(df["t"], unit="s")
df = df.set_index("t")
elif r.status_code == 401:
console.print("[red]Invalid API Key[/red]\n")
else:
console.print(r.text)
return df
@log_start_end(log=logger)
@check_api_key(["API_GLASSNODE_KEY"])
def get_hashrate(
symbol: str,
interval: str = "24h",
start_date: str = "2010-01-01",
end_date: Optional[str] = None,
) -> pd.DataFrame:
"""Returns dataframe with mean hashrate of btc or eth blockchain and symbol price
[Source: https://glassnode.com]
Parameters
----------
symbol : str
Blockchain to check hashrate (BTC or ETH)
start_date : str
Initial date, format YYYY-MM-DD
end_date : str
Final date, format YYYY-MM-DD
interval : str
Interval frequency (e.g., 24h)
Returns
-------
pd.DataFrame
mean hashrate and symbol price over time
"""
if end_date is None:
end_date = datetime.now().strftime("%Y-%m-%d")
ts_start_date = str_date_to_timestamp(start_date)
ts_end_date = str_date_to_timestamp(end_date)
url = api_url + "mining/hash_rate_mean"
url2 = api_url + "market/price_usd_close"
parameters = {
"api_key": cfg.API_GLASSNODE_KEY,
"a": symbol,
"i": interval,
"s": str(ts_start_date),
"u": str(ts_end_date),
}
df = pd.DataFrame()
r = requests.get(url, params=parameters)
r2 = requests.get(url2, params=parameters)
if r.status_code == 200 and r2.status_code == 200:
df = pd.DataFrame(json.loads(r.text))
df2 = pd.DataFrame(json.loads(r2.text))
if df.empty or df2.empty:
console.print(f"No data found for {symbol}'s hashrate or price.\n")
else:
df = df.set_index("t")
df2 = df2.set_index("t")
df.index = pd.to_datetime(df.index, unit="s")
df = df.rename(columns={"v": "hashrate"})
df2.index = pd.to_datetime(df2.index, unit="s")
df2 = df2.rename(columns={"v": "price"})
df = df.merge(df2, left_index=True, right_index=True, how="outer")
elif r.status_code == 401 or r2.status_code == 401:
console.print("[red]Invalid API Key[/red]\n")
else:
if r.status_code != 200:
console.print(f"Error getting hashrate: {r.text}")
if r2.status_code != 200:
console.print(f"Error getting {symbol} price: {r2.text}")
return df
@log_start_end(log=logger)
@check_api_key(["API_GLASSNODE_KEY"])
def get_exchange_balances(
symbol: str,
exchange: str = "aggregated",
start_date: Optional[str] = None,
end_date: Optional[str] = None,
) -> pd.DataFrame:
"""Returns the total amount of coins held on exchange addresses in units and percentage.
[Source: https://glassnode.com]
Parameters
----------
symbol : str
Asset to search active addresses (e.g., BTC)
exchange : str
Exchange to check net position change (possible values are: aggregated, binance, bittrex,
coinex, gate.io, gemini, huobi, kucoin, poloniex, bibox, bigone, bitfinex, hitbtc, kraken,
okex, bithumb, zb.com, cobinhood, bitmex, bitstamp, coinbase, coincheck, luno), by default "aggregated"
start_date : Optional[str], optional
Initial date (format YYYY-MM-DD) by default 2 years ago
end_date : Optional[str], optional
Final date (format YYYY-MM-DD) by default 1 year ago
Returns
-------
pd.DataFrame
total amount of coins in units/percentage and symbol price over time
Examples
--------
>>> from openbb_terminal.sdk import openbb
>>> df = openbb.crypto.dd.eb(symbol="BTC")
"""
if start_date is None:
start_date = (datetime.now() - timedelta(days=365 * 2)).strftime("%Y-%m-%d")
if end_date is None:
end_date = (datetime.now() - timedelta(days=367)).strftime("%Y-%m-%d")
ts_start_date = str_date_to_timestamp(start_date)
ts_end_date = str_date_to_timestamp(end_date)
url = api_url + "distribution/balance_exchanges"
url2 = api_url + "distribution/balance_exchanges_relative"
url3 = api_url + "market/price_usd_close"
parameters = {
"api_key": cfg.API_GLASSNODE_KEY,
"a": symbol,
"i": "24h",
"e": exchange,
"s": str(ts_start_date),
"u": str(ts_end_date),
}
df = pd.DataFrame()
r1 = requests.get(url, params=parameters) # get balances
r2 = requests.get(url2, params=parameters) # get relative (percentage) balances
r3 = requests.get(
url3, params=parameters
) # get price TODO: grab data from loaded symbol
if r1.status_code == 200 and r2.status_code == 200 and r3.status_code == 200:
df1 = pd.DataFrame(json.loads(r1.text))
df1.set_index("t", inplace=True)
df1.rename(columns={"v": "stacked"}, inplace=True)
df2 = pd.DataFrame(json.loads(r2.text))
df2.set_index("t", inplace=True)
df2.rename(columns={"v": "percentage"}, inplace=True)
df3 = pd.DataFrame(json.loads(r3.text))
df3.set_index("t", inplace=True)
df3.rename(columns={"v": "price"}, inplace=True)
df = pd.merge(df1, df2, left_index=True, right_index=True)
df = pd.merge(df, df3, left_index=True, right_index=True)
df.index = pd.to_datetime(df.index, unit="s")
if df.empty or df1.empty or df2.empty or df3.empty:
console.print(f"No data found for {symbol}'s exchange balance or price.\n")
elif r1.status_code == 401 or r2.status_code == 401 or r3.status_code == 401:
console.print("[red]Invalid API Key[/red]\n")
else:
if r1.status_code != 200:
console.print(f"Error getting {symbol}'s exchange balance: {r1.text}")
if r2.status_code != 200:
console.print(
f"Error getting {symbol}'s exchange balance relatives: {r2.text}"
)
if r3.status_code != 200:
console.print(f"Error getting {symbol} price: {r3.text}")
return df
@log_start_end(log=logger)
@check_api_key(["API_GLASSNODE_KEY"])
def get_exchange_net_position_change(
symbol: str,
exchange: str = "binance",
start_date: str = "2010-01-01",
end_date: Optional[str] = None,
) -> pd.DataFrame:
"""Returns 30d change of the supply held in exchange wallets of a certain symbol.
[Source: https://glassnode.com]
Parameters
----------
symbol : str
Asset symbol to search supply (e.g., BTC)
exchange : str
Exchange to check net position change (e.g., binance)
start_date : Optional[str]
Initial date, format YYYY-MM-DD
end_date : Optional[str]
Final date, format YYYY-MM-DD
Returns
-------
pd.DataFrame
supply change in exchange wallets of a certain symbol over time
"""
if end_date is None:
end_date = datetime.now().strftime("%Y-%m-%d")
ts_start_date = str_date_to_timestamp(start_date)
ts_end_date = str_date_to_timestamp(end_date)
url = api_url + "distribution/exchange_net_position_change"
parameters = {
"api_key": cfg.API_GLASSNODE_KEY,
"a": symbol,
"i": "24h",
"e": exchange,
"s": str(ts_start_date),
"u": str(ts_end_date),
}
r = requests.get(url, params=parameters)
df = pd.DataFrame()
if r.status_code == 200:
df = pd.DataFrame(json.loads(r.text))
if df.empty:
console.print(f"No data found for {symbol}'s net position change.\n")
else:
df["t"] = pd.to_datetime(df["t"], unit="s")
df = df.set_index("t")
elif r.status_code == 401:
console.print("[red]Invalid API Key[/red]\n")
else:
console.print(r.text)
return df