diff --git a/gamestonk_terminal/cryptocurrency/due_diligence/glassnode_model.py b/gamestonk_terminal/cryptocurrency/due_diligence/glassnode_model.py index 1c641074309..d75c3479fd8 100644 --- a/gamestonk_terminal/cryptocurrency/due_diligence/glassnode_model.py +++ b/gamestonk_terminal/cryptocurrency/due_diligence/glassnode_model.py @@ -5,6 +5,8 @@ from gamestonk_terminal import config_terminal as cfg api_url = "https://api.glassnode.com/v1/metrics/" +GLASSNODE_SUPPORTED_HASHRATE_ASSETS = ["BTC", "ETH"] + GLASSNODE_SUPPORTED_EXCHANGES = [ "aggregated", "binance", @@ -247,6 +249,52 @@ def get_active_addresses( return pd.DataFrame() +def get_hashrate(asset: str, interval: str, since: int, until: int) -> pd.DataFrame: + """Returns dataframe with mean hashrate of btc or eth blockchain and asset price + [Source: https://glassnode.com] + + Parameters + ---------- + asset : str + Blockchain to check hashrate (BTC or ETH) + since : int + Initial date timestamp (e.g., 1_614_556_800) + until : int + End date timestamp (e.g., 1_614_556_800) + interval : str + Interval frequency (e.g., 24h) + + Returns + ------- + pd.DataFrame + mean hashrate and asset price over time + """ + + url = api_url + "mining/hash_rate_mean" + url2 = api_url + "market/price_usd_close" + + parameters = { + "api_key": cfg.API_GLASSNODE_KEY, + "a": asset, + "i": interval, + "s": str(since), + "u": str(until), + } + + r = requests.get(url, params=parameters) + r2 = requests.get(url2, params=parameters) + if r.status_code == 200 and r2.status_code == 200: + df2 = pd.DataFrame(json.loads(r2.text)) + df = pd.DataFrame(json.loads(r.text)) + df = df.set_index("t") + df.index = pd.to_datetime(df.index, unit="s") + df["price"] = df2["v"].values + df.rename(columns={"v": "hashrate"}, inplace=True) + return df + + return pd.DataFrame() + + def get_exchange_balances( asset: str, exchange: str, interval: str, since: int, until: int ) -> pd.DataFrame: diff --git a/gamestonk_terminal/cryptocurrency/due_diligence/glassnode_view.py b/gamestonk_terminal/cryptocurrency/due_diligence/glassnode_view.py index 5a29a5aaa5d..25ec9d45cb8 100644 --- a/gamestonk_terminal/cryptocurrency/due_diligence/glassnode_view.py +++ b/gamestonk_terminal/cryptocurrency/due_diligence/glassnode_view.py @@ -1,5 +1,5 @@ import os -from matplotlib import pyplot as plt, dates as mdates +from matplotlib import pyplot as plt, dates as mdates, ticker import numpy as np from gamestonk_terminal.helper_funcs import export_data, plot_autoscale from gamestonk_terminal import config_plot as cfgPlot @@ -7,6 +7,7 @@ from gamestonk_terminal.cryptocurrency.due_diligence.glassnode_model import ( get_active_addresses, get_exchange_balances, get_exchange_net_position_change, + get_hashrate, get_non_zero_addresses, ) from gamestonk_terminal import feature_flags as gtff @@ -260,3 +261,63 @@ def display_exchange_balances( "eb", df_balance, ) + + +def display_hashrate( + asset: str, + since: int, + until: int, + interval: str, + export: str = "", +) -> None: + """Display dataframe with mean hashrate of btc or eth blockchain and asset price. + [Source: https://glassnode.org] + + Parameters + ---------- + asset : str + Blockchain to check mean hashrate (BTC or ETH) + since : int + Initial date timestamp (e.g., 1_614_556_800) + until : int + End date timestamp (e.g., 1_614_556_800) + interval : str + Interval frequency (e.g., 24h) + export : str + Export dataframe data to csv,json,xlsx file + """ + + df = get_hashrate(asset, interval, since, until) + + if df.empty: + print("Error in glassnode request") + else: + _, ax1 = plt.subplots(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI) + ax1.plot(df.index, df["hashrate"] / 1_000_000_000_000, c="k") + ax2 = ax1.twinx() + + ax1.grid() + ax2.plot(df.index, df["price"] / 1_000, c="orange") + ax2.yaxis.set_major_formatter(ticker.StrMethodFormatter("${x:.1f}k")) + ax1.yaxis.set_major_formatter(ticker.StrMethodFormatter("{x:.0f}T")) + + ax1.set_ylabel(f"{asset} hashrate (Terahashes/second)") + ax2.set_ylabel(f"{asset} price [$]", c="orange") + ax1.set_title(f"{asset}: Mean hashrate") + ax1.set_xlim(df.index[0], df.index[-1]) + plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%m-%Y")) + plt.gcf().autofmt_xdate() + + if gtff.USE_ION: + plt.ion() + + plt.show() + + print("") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "hr", + df, + ) diff --git a/gamestonk_terminal/cryptocurrency/onchain/onchain_controller.py b/gamestonk_terminal/cryptocurrency/onchain/onchain_controller.py index a72a8d4f3cd..f4c6d06b515 100644 --- a/gamestonk_terminal/cryptocurrency/onchain/onchain_controller.py +++ b/gamestonk_terminal/cryptocurrency/onchain/onchain_controller.py @@ -4,16 +4,25 @@ __docformat__ = "numpy" # pylint: disable=C0302 import argparse +from datetime import datetime, timedelta import difflib from typing import List, Union from colorama.ansi import Style - from prompt_toolkit.completion import NestedCompleter +from gamestonk_terminal.cryptocurrency.due_diligence.glassnode_model import ( + GLASSNODE_SUPPORTED_HASHRATE_ASSETS, + INTERVALS, +) +from gamestonk_terminal.cryptocurrency.due_diligence.glassnode_view import ( + display_hashrate, +) + from gamestonk_terminal import feature_flags as gtff from gamestonk_terminal.menu import session from gamestonk_terminal.helper_funcs import ( + EXPORT_BOTH_RAW_DATA_AND_FIGURES, get_flair, parse_known_args_and_warn, check_positive, @@ -21,6 +30,7 @@ from gamestonk_terminal.helper_funcs import ( try_except, system_clear, EXPORT_ONLY_RAW_DATA_ALLOWED, + valid_date, ) from gamestonk_terminal.cryptocurrency.onchain import ( @@ -66,6 +76,7 @@ class OnchainController: } CHOICES_COMMANDS = [ + "hr", "gwei", "whales", "balance", @@ -101,6 +112,12 @@ class OnchainController: if session and gtff.USE_PROMPT_TOOLKIT: choices: dict = {c: {} for c in self.CHOICES} choices["whales"]["-s"] = {c: None for c in whale_alert_model.FILTERS} + choices["hr"] = {c: None for c in GLASSNODE_SUPPORTED_HASHRATE_ASSETS} + choices["hr"]["-c"] = {c: None for c in GLASSNODE_SUPPORTED_HASHRATE_ASSETS} + choices["hr"]["--coin"] = { + c: None for c in GLASSNODE_SUPPORTED_HASHRATE_ASSETS + } + choices["hr"]["-i"] = {c: None for c in INTERVALS} choices["ttcp"] = {c: None for c in bitquery_model.DECENTRALIZED_EXCHANGES} choices["baas"]["-c"] = {c: None for c in bitquery_model.POSSIBLE_CRYPTOS} choices["baas"]["--coin"] = { @@ -220,6 +237,74 @@ class OnchainController: self.queue.insert(0, "quit") self.queue.insert(0, "quit") + @try_except + def call_hr(self, other_args: List[str]): + """Process hr command""" + + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="hr", + description=""" + Display mean hashrate for a certain blockchain (ETH or BTC) + [Source: https://glassnode.org] + """, + ) + + parser.add_argument( + "-c", + "--coin", + dest="coin", + type=str, + help="Coin to check hashrate (BTC or ETH)", + default="BTC", + choices=GLASSNODE_SUPPORTED_HASHRATE_ASSETS, + ) + + parser.add_argument( + "-i", + "--interval", + dest="interval", + type=str, + help="Frequency interval. Default: 24h", + default="24h", + choices=INTERVALS, + ) + + parser.add_argument( + "-s", + "--since", + dest="since", + type=valid_date, + help="Initial date. Default: 2020-01-01", + default=(datetime.now() - timedelta(days=365)).strftime("%Y-%m-%d"), + ) + + parser.add_argument( + "-u", + "--until", + dest="until", + type=valid_date, + help="Final date. Default: 2021-01-01", + default=(datetime.now()).strftime("%Y-%m-%d"), + ) + + if other_args and not other_args[0][0] == "-": + other_args.insert(0, "-c") + + ns_parser = parse_known_args_and_warn( + parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES + ) + + if ns_parser: + display_hashrate( + asset=ns_parser.coin, + interval=ns_parser.interval, + since=int(datetime.timestamp(ns_parser.since)), + until=int(datetime.timestamp(ns_parser.until)), + export=ns_parser.export, + ) + @try_except def call_gwei(self, other_args: List[str]): """Process gwei command""" @@ -1309,6 +1394,8 @@ class OnchainController: help_text = """ Onchain Menu: +Glassnode: + hr check blockchain hashrate over time (BTC or ETH) Eth Gas Station: gwei check current eth gas fees Whale Alert: diff --git a/scripts/test_crypto_onchain.gst b/scripts/test_crypto_onchain.gst index 0f0a3c3a6a5..1d5234a6bc2 100644 --- a/scripts/test_crypto_onchain.gst +++ b/scripts/test_crypto_onchain.gst @@ -3,4 +3,5 @@ onchain gwei top ueat +hr ETH exit \ No newline at end of file diff --git a/website/content/cryptocurrency/onchain/hr/_index.md b/website/content/cryptocurrency/onchain/hr/_index.md new file mode 100644 index 00000000000..e2c79fc5135 --- /dev/null +++ b/website/content/cryptocurrency/onchain/hr/_index.md @@ -0,0 +1,21 @@ +``` +usage: hr [-c COIN] + [-s INITIAL_DATE] + [-u END_DATE] + [-i {1h, 24h, 10m, 1w, 1month}] + [--export {csv,json,xlsx}] [-h] +``` + +Display the average hash rate of a certain network.[Source: https://glassnode.org] + +Supported assets: BTC, ETH + +``` +arguments: + -c COIN --coin COIN Coin to check hashrate (BTC or ETH) + -s DATE --since DATE Start date (default: 1 year before, e.g., 2020-10-22) + -u DATE --until DATE End date (default: current day, e.g., 2021-10-22) + -i INTERV --interval INTERV Interval frequency (default: 24h) + --export {csv,json,xlsx} Export dataframe data to csv,json,xlsx file (default: ) + -h, --help show this help message (default: False) +``` diff --git a/website/data/menu/main.yml b/website/data/menu/main.yml index 3cced86f24c..e38fc23359a 100755 --- a/website/data/menu/main.yml +++ b/website/data/menu/main.yml @@ -768,6 +768,8 @@ main: sub: - name: gwei ref: "/cryptocurrency/onchain/gwei" + - name: hr + ref: "/cryptocurrency/onchain/hr" - name: address ref: "/cryptocurrency/onchain/address" - name: balance @@ -913,7 +915,7 @@ main: ref: "/economy/fred" sub: - name: search - ref : "/economy/fred/search" + ref: "/economy/fred/search" - name: add ref: "/economy/fred/add" - name: rmv