mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-01 15:01:18 +00:00
* Report fixes * replace wrong typing * mypy * mypy again Co-authored-by: james <jmaslek11@gmail.com>
396 lines
12 KiB
Python
396 lines
12 KiB
Python
import logging
|
|
import os
|
|
from datetime import datetime
|
|
from typing import List, Optional
|
|
|
|
import numpy as np
|
|
from matplotlib import pyplot as plt
|
|
from matplotlib import ticker
|
|
from matplotlib.lines import Line2D
|
|
|
|
from openbb_terminal.config_terminal import theme
|
|
from openbb_terminal.decorators import check_api_key
|
|
from openbb_terminal import config_plot as cfgPlot
|
|
from openbb_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 openbb_terminal.decorators import log_start_end
|
|
from openbb_terminal.helper_funcs import (
|
|
export_data,
|
|
plot_autoscale,
|
|
is_valid_axes_count,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@log_start_end(log=logger)
|
|
@check_api_key(["API_GLASSNODE_KEY"])
|
|
def display_active_addresses(
|
|
symbol: str,
|
|
start_date: str = "2010-01-01",
|
|
end_date: Optional[str] = None,
|
|
interval: str = "24h",
|
|
export: str = "",
|
|
external_axes: Optional[List[plt.Axes]] = None,
|
|
) -> None:
|
|
"""Plots active addresses of a certain symbol over time
|
|
[Source: https://glassnode.org]
|
|
|
|
Parameters
|
|
----------
|
|
symbol : str
|
|
Asset to search active addresses (e.g., BTC)
|
|
start_date : str
|
|
Initial date, format YYYY-MM-DD
|
|
end_date : Optional[str]
|
|
Final date, format YYYY-MM-DD
|
|
interval : str
|
|
Interval frequency (possible values are: 24h, 1w, 1month)
|
|
export : str
|
|
Export dataframe data to csv,json,xlsx file
|
|
external_axes : Optional[List[plt.Axes]], optional
|
|
External axes (1 axis is expected in the list), by default None
|
|
"""
|
|
|
|
if end_date is None:
|
|
end_date = datetime.now().strftime("%Y-%m-%d")
|
|
|
|
df_addresses = get_active_addresses(symbol, interval, start_date, end_date)
|
|
|
|
if df_addresses.empty:
|
|
return
|
|
|
|
# This plot has 1 axis
|
|
if not external_axes:
|
|
_, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
|
|
elif is_valid_axes_count(external_axes, 1):
|
|
(ax,) = external_axes
|
|
else:
|
|
return
|
|
|
|
ax.plot(df_addresses.index, df_addresses["v"] / 1_000, linewidth=1.5)
|
|
|
|
ax.set_title(f"Active {symbol} addresses over time")
|
|
ax.set_ylabel("Addresses [thousands]")
|
|
ax.set_xlim(df_addresses.index[0], df_addresses.index[-1])
|
|
|
|
theme.style_primary_axis(ax)
|
|
|
|
if not external_axes:
|
|
theme.visualize_output()
|
|
|
|
export_data(
|
|
export,
|
|
os.path.dirname(os.path.abspath(__file__)),
|
|
"active",
|
|
df_addresses,
|
|
)
|
|
|
|
|
|
@log_start_end(log=logger)
|
|
@check_api_key(["API_GLASSNODE_KEY"])
|
|
def display_non_zero_addresses(
|
|
symbol: str,
|
|
start_date: str = "2010-01-01",
|
|
end_date: Optional[str] = None,
|
|
export: str = "",
|
|
external_axes: Optional[List[plt.Axes]] = None,
|
|
) -> None:
|
|
"""Plots addresses with non-zero balance of a certain symbol
|
|
[Source: https://glassnode.org]
|
|
|
|
Parameters
|
|
----------
|
|
symbol : str
|
|
Asset to search (e.g., BTC)
|
|
start_date : str
|
|
Initial date, format YYYY-MM-DD
|
|
end_date : Optional[str]
|
|
Final date, format YYYY-MM-DD
|
|
export : str
|
|
Export dataframe data to csv,json,xlsx file
|
|
external_axes : Optional[List[plt.Axes]], optional
|
|
External axes (1 axis is expected in the list), by default None
|
|
"""
|
|
|
|
if end_date is None:
|
|
end_date = datetime.now().strftime("%Y-%m-%d")
|
|
|
|
df_addresses = get_non_zero_addresses(symbol, start_date, end_date)
|
|
|
|
if df_addresses.empty:
|
|
return
|
|
|
|
# This plot has 1 axis
|
|
if not external_axes:
|
|
_, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
|
|
elif is_valid_axes_count(external_axes, 1):
|
|
(ax,) = external_axes
|
|
else:
|
|
return
|
|
|
|
ax.plot(df_addresses.index, df_addresses["v"] / 1_000, linewidth=1.5)
|
|
|
|
ax.set_title(f"{symbol} Addresses with non-zero balances")
|
|
ax.set_ylabel("Number of Addresses")
|
|
ax.set_xlim(df_addresses.index[0], df_addresses.index[-1])
|
|
|
|
theme.style_primary_axis(ax)
|
|
|
|
if not external_axes:
|
|
theme.visualize_output()
|
|
|
|
export_data(
|
|
export,
|
|
os.path.dirname(os.path.abspath(__file__)),
|
|
"nonzero",
|
|
df_addresses,
|
|
)
|
|
|
|
|
|
@log_start_end(log=logger)
|
|
@check_api_key(["API_GLASSNODE_KEY"])
|
|
def display_exchange_net_position_change(
|
|
symbol: str,
|
|
exchange: str = "binance",
|
|
start_date: str = "2010-01-01",
|
|
end_date: Optional[str] = None,
|
|
export: str = "",
|
|
external_axes: Optional[List[plt.Axes]] = None,
|
|
) -> None:
|
|
"""Plots 30d change of the supply held in exchange wallets.
|
|
[Source: https://glassnode.org]
|
|
|
|
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)
|
|
start_date : str
|
|
Initial date, format YYYY-MM-DD
|
|
end_date : Optional[str]
|
|
Final date, format YYYY-MM-DD
|
|
export : str
|
|
Export dataframe data to csv,json,xlsx file
|
|
external_axes : Optional[List[plt.Axes]], optional
|
|
External axes (1 axis is expected in the list), by default None
|
|
"""
|
|
|
|
if end_date is None:
|
|
end_date = datetime.now().strftime("%Y-%m-%d")
|
|
|
|
df_addresses = get_exchange_net_position_change(
|
|
symbol, exchange, start_date, end_date
|
|
)
|
|
|
|
if df_addresses.empty:
|
|
return
|
|
|
|
# This plot has 1 axis
|
|
if not external_axes:
|
|
_, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
|
|
elif is_valid_axes_count(external_axes, 1):
|
|
(ax,) = external_axes
|
|
else:
|
|
return
|
|
|
|
ax.fill_between(
|
|
df_addresses[df_addresses["v"] < 0].index,
|
|
df_addresses[df_addresses["v"] < 0]["v"].values / 1e3,
|
|
np.zeros(len(df_addresses[df_addresses["v"] < 0])),
|
|
facecolor=theme.down_color,
|
|
)
|
|
ax.fill_between(
|
|
df_addresses[df_addresses["v"] >= 0].index,
|
|
df_addresses[df_addresses["v"] >= 0]["v"].values / 1e3,
|
|
np.zeros(len(df_addresses[df_addresses["v"] >= 0])),
|
|
facecolor=theme.up_color,
|
|
)
|
|
|
|
ax.set_ylabel(f"30d change of {symbol} supply held in exchange wallets [thousands]")
|
|
ax.set_title(
|
|
f"{symbol}: Exchange Net Position Change - {'all exchanges' if exchange == 'aggregated' else exchange}"
|
|
)
|
|
ax.set_xlim(df_addresses.index[0], df_addresses.index[-1])
|
|
|
|
theme.style_primary_axis(ax)
|
|
|
|
if not external_axes:
|
|
theme.visualize_output()
|
|
|
|
export_data(
|
|
export,
|
|
os.path.dirname(os.path.abspath(__file__)),
|
|
"change",
|
|
df_addresses,
|
|
)
|
|
|
|
|
|
@log_start_end(log=logger)
|
|
@check_api_key(["API_GLASSNODE_KEY"])
|
|
def display_exchange_balances(
|
|
symbol: str,
|
|
exchange: str = "aggregated",
|
|
start_date: Optional[str] = None,
|
|
end_date: Optional[str] = None,
|
|
percentage: bool = False,
|
|
export: str = "",
|
|
external_axes: Optional[List[plt.Axes]] = None,
|
|
) -> None:
|
|
"""Plots total amount of coins held on exchange addresses in units and percentage.
|
|
[Source: https://glassnode.org]
|
|
|
|
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
|
|
percentage : bool
|
|
Show percentage instead of stacked value.
|
|
export : str
|
|
Export dataframe data to csv,json,xlsx file
|
|
external_axes : Optional[List[plt.Axes]], optional
|
|
External axes (2 axes are expected in the list), by default None
|
|
|
|
Examples
|
|
--------
|
|
>>> from openbb_terminal.sdk import openbb
|
|
>>> openbb.crypto.dd.eb_chart(symbol="BTC")
|
|
"""
|
|
|
|
df_balance = get_exchange_balances(symbol, exchange, start_date, end_date)
|
|
|
|
if df_balance.empty:
|
|
return
|
|
|
|
# This plot has 2 axes
|
|
if not external_axes:
|
|
_, ax1 = plt.subplots(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
|
|
ax2 = ax1.twinx()
|
|
|
|
elif is_valid_axes_count(external_axes, 2):
|
|
(ax1, ax2) = external_axes
|
|
else:
|
|
return
|
|
|
|
if percentage:
|
|
ax1.plot(df_balance.index, df_balance["percentage"] * 100)
|
|
else:
|
|
ax1.plot(df_balance.index, df_balance["stacked"] / 1000)
|
|
|
|
ax1.set_ylabel(f"{symbol} units [{'%' if percentage else 'thousands'}]")
|
|
ax1.set_title(
|
|
f"{symbol}: Total Balance in {'all exchanges' if exchange == 'aggregated' else exchange}"
|
|
)
|
|
ax1.tick_params(axis="x", labelrotation=10)
|
|
ax1.legend([f"{symbol} Unit"], loc="upper right")
|
|
|
|
ax2.grid(visible=False)
|
|
ax2.plot(df_balance.index, df_balance["price"], color="orange")
|
|
ax2.set_ylabel(f"{symbol} price [$]")
|
|
ax2.legend([f"{symbol} Price"], loc="upper left")
|
|
|
|
if not external_axes:
|
|
theme.visualize_output()
|
|
|
|
export_data(
|
|
export,
|
|
os.path.dirname(os.path.abspath(__file__)),
|
|
"eb",
|
|
df_balance,
|
|
)
|
|
|
|
|
|
@log_start_end(log=logger)
|
|
@check_api_key(["API_GLASSNODE_KEY"])
|
|
def display_hashrate(
|
|
symbol: str,
|
|
start_date: str = "2010-01-01",
|
|
end_date: Optional[str] = None,
|
|
interval: str = "24h",
|
|
export: str = "",
|
|
external_axes: Optional[List[plt.Axes]] = None,
|
|
) -> None:
|
|
"""Plots dataframe with mean hashrate of btc or eth blockchain and symbol price.
|
|
[Source: https://glassnode.org]
|
|
|
|
Parameters
|
|
----------
|
|
symbol : str
|
|
Blockchain to check mean hashrate (BTC or ETH)
|
|
start_date : str
|
|
Initial date, format YYYY-MM-DD
|
|
end_date : Optional[str]
|
|
Final date, format YYYY-MM-DD
|
|
interval : str
|
|
Interval frequency (possible values are: 24, 1w, 1month)
|
|
export : str
|
|
Export dataframe data to csv,json,xlsx file
|
|
external_axes : Optional[List[plt.Axes]], optional
|
|
External axes (2 axes are expected in the list), by default None
|
|
"""
|
|
|
|
if end_date is None:
|
|
end_date = datetime.now().strftime("%Y-%m-%d")
|
|
|
|
df = get_hashrate(symbol, interval, start_date, end_date)
|
|
|
|
if df.empty:
|
|
return
|
|
|
|
# This plot has 2 axes
|
|
if not external_axes:
|
|
_, ax1 = plt.subplots(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
|
|
ax2 = ax1.twinx()
|
|
|
|
elif is_valid_axes_count(external_axes, 2):
|
|
(ax1, ax2) = external_axes
|
|
else:
|
|
return
|
|
|
|
ax1.plot(
|
|
df.index, df["hashrate"] / 1_000_000_000_000, color=theme.down_color, lw=0.8
|
|
)
|
|
ax1.yaxis.set_major_formatter(ticker.StrMethodFormatter("{x:.0f}T"))
|
|
ax1.set_ylabel(f"{symbol} hashrate (Terahashes/second)")
|
|
ax1.set_title(f"{symbol}: Mean hashrate")
|
|
ax1.tick_params(axis="x", labelrotation=10)
|
|
|
|
ax2.set_xlim(left=df.index[0])
|
|
ax2.grid(visible=False)
|
|
ax2.plot(df.index, df["price"] / 1_000, color=theme.up_color, lw=0.8)
|
|
ax2.yaxis.set_major_formatter(ticker.StrMethodFormatter("${x:.1f}k"))
|
|
ax2.set_ylabel(f"{symbol} price [$]")
|
|
|
|
# Manually construct the chart legend
|
|
lines = [
|
|
Line2D([0], [0], color=color) for color in [theme.up_color, theme.down_color]
|
|
]
|
|
labels = ["Price", "Hash Rate"]
|
|
ax2.legend(lines, labels)
|
|
|
|
if not external_axes:
|
|
theme.visualize_output()
|
|
|
|
export_data(
|
|
export,
|
|
os.path.dirname(os.path.abspath(__file__)),
|
|
"hr",
|
|
df,
|
|
)
|