diff --git a/gamestonk_terminal/cryptocurrency/crypto_controller.py b/gamestonk_terminal/cryptocurrency/crypto_controller.py index 0d42c795e08..c7e7e318e4f 100644 --- a/gamestonk_terminal/cryptocurrency/crypto_controller.py +++ b/gamestonk_terminal/cryptocurrency/crypto_controller.py @@ -17,6 +17,7 @@ from gamestonk_terminal.helper_funcs import ( from gamestonk_terminal.menu import session from gamestonk_terminal.cryptocurrency.technical_analysis import ta_controller from gamestonk_terminal.cryptocurrency.overview import overview_controller +from gamestonk_terminal.cryptocurrency.defi import defi_controller from gamestonk_terminal.cryptocurrency.due_diligence import ( dd_controller, coinpaprika_view, @@ -60,7 +61,7 @@ class CryptoController: "find", ] - CHOICES_MENUS = ["ta", "dd", "ov", "disc", "report", "onchain"] + CHOICES_MENUS = ["ta", "dd", "ov", "disc", "report", "onchain", "defi"] SOURCES = { "bin": "Binance", @@ -121,6 +122,7 @@ Note: Some of CoinGecko commands can fail. Team is working on fix. > dd due-diligence for loaded coin, e.g.: coin information, social media, market stats > ta technical analysis for loaded coin, e.g.: ema, macd, rsi, adx, bbands, obv > onchain information on different blockchains, e.g.: eth gas fees +> defi decentralized finance information, e.g.: dpi, llama, tvl, lending, borrow, funding > report generate automatic report """ print(help_text) @@ -664,6 +666,14 @@ Note: Some of CoinGecko commands can fail. Team is working on fix. else: return True + def call_defi(self, _): + """Process defi command""" + defi = defi_controller.menu() + if defi is False: + self.print_help() + else: + return True + def call_finbrain(self, other_args): """Process finbrain command""" parser = argparse.ArgumentParser( diff --git a/gamestonk_terminal/cryptocurrency/defi/__init__.py b/gamestonk_terminal/cryptocurrency/defi/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gamestonk_terminal/cryptocurrency/defi/defi_controller.py b/gamestonk_terminal/cryptocurrency/defi/defi_controller.py new file mode 100644 index 00000000000..ebee79c25bd --- /dev/null +++ b/gamestonk_terminal/cryptocurrency/defi/defi_controller.py @@ -0,0 +1,537 @@ +"""Defi Controller Module""" +__docformat__ = "numpy" + +import os +import argparse +from typing import List +from prompt_toolkit.completion import NestedCompleter + +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.menu import session +from gamestonk_terminal.helper_funcs import ( + get_flair, + parse_known_args_and_warn, + check_positive, +) + +from gamestonk_terminal.cryptocurrency.defi import ( + defirate_view, + defipulse_view, + llama_view, + substack_view, +) + +# pylint: disable=R1732 + + +class DefiController: + """Defi Controller class""" + + CHOICES = [ + "cls", + "?", + "help", + "q", + "quit", + ] + + CHOICES_COMMANDS = [ + "dpi", + "funding", + "lending", + "tvl", + "borrow", + "llama", + "newsletter", + ] + + CHOICES += CHOICES_COMMANDS + + def __init__(self): + """Constructor""" + self.defi_parser = argparse.ArgumentParser(add_help=False, prog="defi") + self.defi_parser.add_argument( + "cmd", + choices=self.CHOICES, + ) + + def switch(self, an_input: str): + """Process and dispatch input + + Parameters + ------- + an_input : str + string with input arguments + + Returns + ------- + True, False or None + False - quit the menu + True - quit the program + None - continue in the menu + """ + + # Empty command + if not an_input: + print("") + return None + + (known_args, other_args) = self.defi_parser.parse_known_args(an_input.split()) + + # Help menu again + if known_args.cmd == "?": + print_help() + return None + + # Clear screen + if known_args.cmd == "cls": + os.system("cls||clear") + return None + + return getattr( + self, "call_" + known_args.cmd, lambda: "Command not recognized!" + )(other_args) + + def call_help(self, *_): + """Process Help command""" + print_help() + + def call_q(self, _): + """Process Q command - quit the menu""" + return False + + def call_quit(self, _): + """Process Quit command - quit the program""" + return True + + def call_dpi(self, other_args: List[str]): + """Process dpi command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="dpi", + description=""" + Displays DeFi Pulse crypto protocols. + [Source: https://defipulse.com/] + """, + ) + + parser.add_argument( + "-t", + "--top", + dest="top", + type=check_positive, + help="top N number records", + default=15, + ) + + parser.add_argument( + "-s", + "--sort", + dest="sortby", + type=str, + help="Sort by given column. Default: Rank", + default="Rank", + choices=["Rank", "Name", "Chain", "Category", "TVL", "Change_1D"], + ) + + parser.add_argument( + "--descend", + action="store_false", + help="Flag to sort in descending order (lowest first)", + dest="descend", + default=True, + ) + + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + + if not ns_parser: + return + + defipulse_view.display_defipulse( + top=ns_parser.top, + sortby=ns_parser.sortby, + descend=ns_parser.descend, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") + + def call_llama(self, other_args: List[str]): + """Process llama command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="llama", + description=""" + Display information about listed DeFi Protocols on DeFi Llama. + [Source: https://docs.llama.fi/api] + """, + ) + + parser.add_argument( + "-t", + "--top", + dest="top", + type=check_positive, + help="top N number records", + default=10, + ) + + parser.add_argument( + "-s", + "--sort", + dest="sortby", + type=str, + help="Sort by given column. Default: tvl", + default="tvl", + choices=[ + "tvl", + "symbol", + "category", + "chains", + "change_1h", + "change_1d", + "change_7d", + "tvl", + ], + ) + + parser.add_argument( + "--descend", + action="store_false", + help="Flag to sort in descending order (lowest first)", + dest="descend", + default=False, + ) + + parser.add_argument( + "--desc", + action="store_false", + help="Flag to display description of protocol", + dest="description", + default=False, + ) + + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + + if not ns_parser: + return + + llama_view.display_defi_protocols( + top=ns_parser.top, + sortby=ns_parser.sortby, + descend=ns_parser.descend, + description=ns_parser.description, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") + + def call_tvl(self, other_args: List[str]): + """Process tvl command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="tvl", + description=""" + Displays historical values of the total sum of TVLs from all listed protocols. + [Source: https://docs.llama.fi/api] + """, + ) + + parser.add_argument( + "-t", + "--top", + dest="top", + type=check_positive, + help="top N number records", + default=10, + ) + + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + + if not ns_parser: + return + + llama_view.display_defi_tvl(top=ns_parser.top, export=ns_parser.export) + + except Exception as e: + print(e, "\n") + + def call_funding(self, other_args: List[str]): + """Process funding command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="funding", + description=""" + Display Funding rates. + [Source: https://defirate.com/] + """, + ) + + parser.add_argument( + "-t", + "--top", + dest="top", + type=check_positive, + help="top N number records", + default=10, + ) + + parser.add_argument( + "--current", + action="store_false", + default=True, + dest="current", + help="Show Current Funding Rates or Last 30 Days Average", + ) + + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + + if not ns_parser: + return + + defirate_view.display_funding_rates( + top=ns_parser.top, current=ns_parser.current, export=ns_parser.export + ) + + except Exception as e: + print(e, "\n") + + def call_borrow(self, other_args: List[str]): + """Process borrow command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="borrow", + description=""" + Display DeFi borrow rates. + [Source: https://defirate.com/] + """, + ) + + parser.add_argument( + "-t", + "--top", + dest="top", + type=check_positive, + help="top N number records", + default=10, + ) + + parser.add_argument( + "--current", + action="store_false", + default=True, + dest="current", + help="Show Current Borrow Rates or Last 30 Days Average", + ) + + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + + if not ns_parser: + return + + defirate_view.display_borrow_rates( + top=ns_parser.top, current=ns_parser.current, export=ns_parser.export + ) + + except Exception as e: + print(e, "\n") + + def call_lending(self, other_args: List[str]): + """Process lending command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="lending", + description=""" + Display DeFi lending rates. + [Source: https://defirate.com/] + """, + ) + + parser.add_argument( + "-t", + "--top", + dest="top", + type=check_positive, + help="top N number records", + default=15, + ) + + parser.add_argument( + "--current", + action="store_false", + default=True, + dest="current", + help="Show Current Lending Rates or Last 30 Days Average", + ) + + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + + if not ns_parser: + return + + defirate_view.dislpay_lending_rates( + top=ns_parser.top, current=ns_parser.current, export=ns_parser.export + ) + + except Exception as e: + print(e, "\n") + + def call_newsletter(self, other_args: List[str]): + """Process newsletter command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="newsletter", + description=""" + Display DeFi related substack newsletters. + [Source: substack.com] + """, + ) + + parser.add_argument( + "-t", + "--top", + dest="top", + type=check_positive, + help="top N number records", + default=10, + ) + + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + + if not ns_parser: + return + + substack_view.display_newsletters( + top=ns_parser.top, export=ns_parser.export + ) + + except Exception as e: + print(e, "\n") + + +def print_help(): + """Print help""" + print("\nDecentralized Finance:") + print(" cls clear screen") + print(" ?/help show this menu again") + print(" q quit this menu, and shows back to main menu") + print(" quit quit to abandon program") + print("") + print(" llama DeFi protocols listed on DeFi Llama") + print(" tvl Total value locked of DeFi protocols") + print(" newsletter Recent DeFi related newsletters") + print(" dpi DeFi protocols listed on DefiPulse") + print(" funding Funding reates - current or last 30 days average") + print(" borrow DeFi borrow rates - current or last 30 days average") + print(" lending DeFi ending rates - current or last 30 days average") + print("") + + +def menu(): + """Defi Menu""" + defi_controller = DefiController() + defi_controller.call_help(None) + + while True: + # Get input command from user + if session and gtff.USE_PROMPT_TOOLKIT: + completer = NestedCompleter.from_nested_dict( + {c: None for c in defi_controller.CHOICES} + ) + an_input = session.prompt( + f"{get_flair()} (crypto)>(defi)> ", + completer=completer, + ) + else: + an_input = input(f"{get_flair()} (crypto)>(defi)> ") + + try: + process_input = defi_controller.switch(an_input) + except SystemExit: + print("The command selected doesn't exist\n") + continue + + if process_input is False: + return False + + if process_input is True: + return True diff --git a/gamestonk_terminal/cryptocurrency/defi/defipulse_model.py b/gamestonk_terminal/cryptocurrency/defi/defipulse_model.py new file mode 100644 index 00000000000..0c5f56c096f --- /dev/null +++ b/gamestonk_terminal/cryptocurrency/defi/defipulse_model.py @@ -0,0 +1,50 @@ +"""DeFi Pulse model""" +__docformat__ = "numpy" + +import requests +from bs4 import BeautifulSoup +import pandas as pd + + +def get_defipulse_index() -> pd.DataFrame: + """Scrapes data from DeFi Pulse with all DeFi Pulse crypto protocols. + [Source: https://defipulse.com/] + + Returns + ------- + pd.DataFrame + List of DeFi Pulse protocols. + """ + + req = requests.get("https://defipulse.com/") + result = req.content.decode("utf8") + soup = BeautifulSoup(result, features="lxml") + table = soup.find("tbody").find_all("tr") + + list_of_records = [] + for row in table: + row_elements = [] + for element in row.find_all("td"): + text = element.text + row_elements.append(text) + list_of_records.append(row_elements) + + df = pd.DataFrame( + list_of_records, + columns=[ + "x", + "Rank", + "Name", + "Chain", + "Category", + "TVL", + "Change_1D", + ], + ) + try: + df["Rank"] = df["Rank"].apply(lambda x: int(x.replace(".", ""))) + except Exception as e: + print(e, "\n") + + df.drop("x", axis=1, inplace=True) + return df diff --git a/gamestonk_terminal/cryptocurrency/defi/defipulse_view.py b/gamestonk_terminal/cryptocurrency/defi/defipulse_view.py new file mode 100644 index 00000000000..1f94fdb3a37 --- /dev/null +++ b/gamestonk_terminal/cryptocurrency/defi/defipulse_view.py @@ -0,0 +1,51 @@ +"""DeFi Pulse view""" +__docformat__ = "numpy" + +import os +from tabulate import tabulate +from gamestonk_terminal.cryptocurrency.defi import defipulse_model +from gamestonk_terminal.helper_funcs import export_data +from gamestonk_terminal import feature_flags as gtff + + +def display_defipulse(top: int, sortby: str, descend: bool, export: str = "") -> None: + """Displays all DeFi Pulse crypto protocols. + [Source: https://defipulse.com/] + + Parameters + ---------- + top: int + Number of records to display + sortby: str + Key by which to sort data + descend: bool + Flag to sort data descending + export : str + Export dataframe data to csv,json,xlsx file + """ + + df = defipulse_model.get_defipulse_index() + df_data = df.copy() + + df = df.sort_values(by=sortby, ascending=descend) + + if gtff.USE_TABULATE_DF: + print( + tabulate( + df.head(top), + headers=df.columns, + floatfmt=".2f", + showindex=False, + tablefmt="fancy_grid", + ), + "\n", + ) + else: + print(df.to_string, "\n") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "dpi", + df_data, + ) diff --git a/gamestonk_terminal/cryptocurrency/defi/defirate_model.py b/gamestonk_terminal/cryptocurrency/defi/defirate_model.py new file mode 100644 index 00000000000..41fdb7d0de4 --- /dev/null +++ b/gamestonk_terminal/cryptocurrency/defi/defirate_model.py @@ -0,0 +1,122 @@ +"""DeFi Rate model""" +__docformat__ = "numpy" + +import requests +from bs4 import BeautifulSoup +from bs4.element import Tag +import pandas as pd + + +def _scrape_defirate(url: str, current: bool = True) -> Tag: + """Helper method that scrapes table object from defirate.com + [Source: https://defirate.com/] + + Parameters + ---------- + url: str + Defirate url + current: bool + Flag that indicates if you want to scrape current data or last 30 days average. + + Returns + ------- + bs4.element.Tag + Table with scraped data + """ + + current_or_average_map = { + True: "table-container", + False: "table-container table-hidden", + } + + div_class = current_or_average_map[current] + req = requests.get(url) + soup = BeautifulSoup(req.text, features="lxml") + table = soup.find("div", class_=div_class).find("table") + return table + + +def get_funding_rates(current: bool = True) -> pd.DataFrame: + """Funding rates are transfer payments made between long and short positions on perpetual swap futures markets. + They’re designed to keep contract prices consistent with the underlying asset. + [Source: https://defirate.com/] + + Parameters + ---------- + current: bool + If true displays current funding rate values. If false displays last 30 day average of funding rates. + + ------- + pd.DataFrame + Dataframe with funding rates. + """ + + url = "https://defirate.com/funding/" + table = _scrape_defirate(url, current) + items = [] + first_row = table.find("thead").text.strip().split() + headers = [r for r in first_row if r != "Trade"] + headers.insert(0, "Symbol") + for i in table.find_all("td"): + items.append(i.text.strip()) + fundings = [items[i : i + 5] for i in range(0, len(items), 5)] + return pd.DataFrame(columns=headers, data=fundings) + + +def get_lending_rates(current: bool = True) -> pd.DataFrame: + """Decentralized Finance lending – or DeFi lending for short – allows users to supply cryptocurrencies + in exchange for earning an annualized return. + [Source: https://defirate.com] + + Parameters + ---------- + current: bool + If true displays current lending rates values. If false displays last 30 day average of lending rates. + + Returns + ------- + pd.DataFrame + Dataframe with lending rates. + """ + + url = "https://defirate.com/loans/?exchange_table_type=lend" + table = _scrape_defirate(url, current) + items = [] + first_row = table.find("thead").text.strip().split("\n") + + headers = [r for r in first_row if r not in ["Lend", ""]] + headers.insert(0, "Symbol") + for i in table.find_all("td"): + items.append(i.text.strip()) + lendings = [items[i : i + 12] for i in range(0, len(items), 12)] + return pd.DataFrame(columns=headers, data=lendings) + + +def get_borrow_rates(current: bool = True) -> pd.DataFrame: + """One aspect of Decentralized Finance (DeFi) is the ability to take out a loan on top cryptocurrencies at any time + in an entirely permissionless fashion.By using smart contracts, borrowers are able to lock collateral to protect + against defaults while seamlesslyadding to or closing their loans at any time. + [Source: https://defirate.com] + + Parameters + ---------- + current: bool + If true displays current borrow rates values. If false displays last 30 day average of borrow rates. + + Returns + ------- + pd.DataFrame + Dataframe with borrow rates + """ + + url = "https://defirate.com/loans/?exchange_table_type=borrow" + table = _scrape_defirate(url, current) + items = [] + first_row = table.find("thead").text.strip().split("\n") + + headers = [r for r in first_row if r not in ["Borrow", ""]] + headers.insert(0, "Symbol") + for i in table.find_all("td"): + items.append(i.text.strip()) + borrowings = [items[i : i + 12] for i in range(0, len(items), 12)] + return pd.DataFrame(columns=headers, data=borrowings) diff --git a/gamestonk_terminal/cryptocurrency/defi/defirate_view.py b/gamestonk_terminal/cryptocurrency/defi/defirate_view.py new file mode 100644 index 00000000000..99cebb283bf --- /dev/null +++ b/gamestonk_terminal/cryptocurrency/defi/defirate_view.py @@ -0,0 +1,129 @@ +"""DeFi Rate View""" +__docformat__ = "numpy" + +import os +from tabulate import tabulate +from gamestonk_terminal.helper_funcs import export_data +from gamestonk_terminal.cryptocurrency.defi import defirate_model +from gamestonk_terminal import feature_flags as gtff + + +def display_funding_rates(top: int, current: bool = True, export: str = "") -> None: + """Display Funding rates - transfer payments made between long and short positions on perpetual swap futures markets + [Source: https://defirate.com/] + + Parameters + ---------- + top: int + Number of records to display + current: bool + If true displays current funding rate values. If false displays last 30 day average of funding rates. + export : str + Export dataframe data to csv,json,xlsx file + """ + + df = defirate_model.get_funding_rates(current) + + df_data = df.copy() + + if gtff.USE_TABULATE_DF: + print( + tabulate( + df.head(top), + headers=df.columns, + floatfmt=".2f", + showindex=False, + tablefmt="fancy_grid", + ), + "\n", + ) + else: + print(df.to_string, "\n") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "funding", + df_data, + ) + + +def dislpay_lending_rates(top: int, current: bool = True, export: str = "") -> None: + """Displays top DeFi lendings. Decentralized Finance lending – allows users to supply cryptocurrencies + in exchange for earning an annualized return + [Source: https://defirate.com/] + + Parameters + ---------- + top: int + Number of records to display + current: bool + If true displays current funding rate values. If false displays last 30 day average of funding rates. + export : str + Export dataframe data to csv,json,xlsx file + """ + + df = defirate_model.get_lending_rates(current) + df_data = df.copy() + df = df.loc[:, ~df.eq("–").all()] + + if gtff.USE_TABULATE_DF: + print( + tabulate( + df.head(top), + headers=df.columns, + floatfmt=".2f", + showindex=False, + tablefmt="fancy_grid", + ), + "\n", + ) + else: + print(df.to_string, "\n") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "lending", + df_data, + ) + + +def display_borrow_rates(top: int, current: bool = True, export: str = "") -> None: + """Displays DeFi borrow rates. By using smart contracts, borrowers are able to lock collateral + to protect against defaults while seamlessly adding to or closing their loans at any time. + [Source: https://defirate.com/] + + Parameters + ---------- + top: int + Number of records to display + current: bool + If true displays current funding rate values. If false displays last 30 day average of funding rates. + export : str + Export dataframe data to csv,json,xlsx file + """ + df = defirate_model.get_borrow_rates(current) + df_data = df.copy() + df = df.loc[:, ~df.eq("–").all()] + + if gtff.USE_TABULATE_DF: + print( + tabulate( + df.head(top), + headers=df.columns, + floatfmt=".2f", + showindex=False, + tablefmt="fancy_grid", + ), + "\n", + ) + else: + print(df.to_string, "\n") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "borrow", + df_data, + ) diff --git a/gamestonk_terminal/cryptocurrency/defi/llama_model.py b/gamestonk_terminal/cryptocurrency/defi/llama_model.py new file mode 100644 index 00000000000..3d18652dca0 --- /dev/null +++ b/gamestonk_terminal/cryptocurrency/defi/llama_model.py @@ -0,0 +1,68 @@ +"""Llama model""" +__docformat__ = "numpy" + +from datetime import datetime +import textwrap +import requests +import pandas as pd +import numpy as np + + +def get_defi_protocols() -> pd.DataFrame: + """Returns information about listed DeFi protocols, their current TVL and changes to it in the last hour/day/week. + [Source: https://docs.llama.fi/api] + + Returns + ------- + pd.DataFrame + Information about DeFi protocols + """ + + response = requests.get("https://api.llama.fi/protocols") + columns = [ + "name", + "symbol", + "category", + "chains", + "change_1h", + "change_1d", + "change_7d", + "tvl", + "url", + "description", + ] + if response.status_code != 200: + raise Exception(f"Status code: {response.status_code}. Resason {response.text}") + try: + df = pd.DataFrame(response.json()) + df.replace({float(np.nan): None}, inplace=True) + df["chains"] = df["chains"].apply( + lambda x: "\n".join(textwrap.wrap(", ".join(x), width=50)) + ) + df["description"] = df["description"].apply( + lambda x: "\n".join(textwrap.wrap(x, width=70)) if isinstance(x, str) else x + ) + return df[columns] + + except Exception as e: + raise ValueError("Wrong response type\n") from e + + +def get_defi_tvl() -> pd.DataFrame: + """Returns historical values of the total sum of TVLs from all listed protocols. + [Source: https://docs.llama.fi/api] + + Returns + ------- + pd.DataFrame + Historical values of total sum of Total Value Locked from all listed protocols. + """ + response = requests.get("https://api.llama.fi/charts") + if response.status_code != 200: + raise Exception(f"Status code: {response.status_code}. Resason {response.text}") + try: + df = pd.DataFrame(response.json()) + df["date"] = df["date"].apply(lambda x: datetime.fromtimestamp(int(x)).date()) + return df + except Exception as e: + raise ValueError("Wrong response data") from e diff --git a/gamestonk_terminal/cryptocurrency/defi/llama_view.py b/gamestonk_terminal/cryptocurrency/defi/llama_view.py new file mode 100644 index 00000000000..8b6aa84653f --- /dev/null +++ b/gamestonk_terminal/cryptocurrency/defi/llama_view.py @@ -0,0 +1,116 @@ +"""Llama View""" +__docformat__ = "numpy" + +import os +from tabulate import tabulate +import matplotlib.pyplot as plt +from gamestonk_terminal.cryptocurrency.defi import llama_model +from gamestonk_terminal.helper_funcs import ( + export_data, + plot_autoscale, + long_number_format, +) +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.config_plot import PLOT_DPI + + +def display_defi_protocols( + top: int, sortby: str, descend: bool, description: bool, export: str = "" +) -> None: + """Display information about listed DeFi protocols, their current TVL and changes to it in the last hour/day/week. + [Source: https://docs.llama.fi/api] + + Parameters + ---------- + top: int + Number of records to display + sortby: str + Key by which to sort data + descend: bool + Flag to sort data descending + description: bool + Flag to display description of protocol + export : str + Export dataframe data to csv,json,xlsx file + """ + + df = llama_model.get_defi_protocols() + df_data = df.copy() + + df = df.sort_values(by=sortby, ascending=descend) + + df["tvl"] = df["tvl"].apply(lambda x: long_number_format(x)) + + if not description: + df.drop(["description", "url"], axis=1, inplace=True) + else: + df = df[ + [ + "name", + "symbol", + "category", + "description", + "url", + ] + ] + + if gtff.USE_TABULATE_DF: + print( + tabulate( + df.head(top), + headers=df.columns, + floatfmt=".2f", + showindex=False, + tablefmt="fancy_grid", + ), + "\n", + ) + else: + print(df.to_string, "\n") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "llama", + df_data, + ) + + +def display_defi_tvl(top: int, export: str = "") -> None: + """Displays historical values of the total sum of TVLs from all listed protocols. + [Source: https://docs.llama.fi/api] + + Parameters + ---------- + top: int + Number of records to display + export : str + Export dataframe data to csv,json,xlsx file + """ + + df = llama_model.get_defi_tvl() + df_data = df.copy() + + df = df.tail(top) + df["totalLiquidityUSD"] = df["totalLiquidityUSD"] / 1_000_000_000 + + plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) + + plt.plot(df["date"], df["totalLiquidityUSD"], "-ok", ms=2) + plt.xlabel("Time") + plt.xlim(df["date"].iloc[0], df["date"].iloc[-1]) + plt.gcf().autofmt_xdate() + plt.ylabel("Total Value Locked USD [1B]") + plt.grid(b=True, which="major", color="#666666", linestyle="-") + plt.minorticks_on() + plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + plt.title("Total Value Locked in DeFi [Billions USD]") + plt.show() + print("") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "tvl", + df_data, + ) diff --git a/gamestonk_terminal/cryptocurrency/defi/substack_model.py b/gamestonk_terminal/cryptocurrency/defi/substack_model.py new file mode 100644 index 00000000000..e2e2fdb722f --- /dev/null +++ b/gamestonk_terminal/cryptocurrency/defi/substack_model.py @@ -0,0 +1,86 @@ +"""Substack model""" +__docformat__ = "numpy" + +import textwrap +import concurrent.futures +import pandas as pd +import requests +from bs4 import BeautifulSoup +from dateutil import parser + + +def scrape_substack(url: str) -> list: + """Helper method to scrape newsletters from substack. + [Source: substack.com] + + Parameters + ---------- + url: str + Url to newsletter on substack domain. + + Returns + ------- + list + list of news from given newsletter + """ + + req = requests.get(url) + soup = BeautifulSoup(req.text, features="lxml") + results = [] + posts = soup.find("div", class_="portable-archive-list").find_all( + "div", class_="post-preview portable-archive-post has-image has-author-line" + ) + for post in posts: + title, url, time = ( + post.a.text, + post.a["href"], + post.find("time").get("datetime"), + ) + results.append([title, url, time]) + return results + + +def get_newsletters() -> pd.DataFrame: + """Scrape all substack newsletters from url list. + [Source: substack.com] + + Returns + ------- + pd.DataFrame + DataFrame with recent news from most popular DeFi related newsletters. + """ + + urls = [ + "https://defiweekly.substack.com/archive", + "https://newsletter.thedefiant.io/archive", + "https://thedailygwei.substack.com/archive", + "https://todayindefi.substack.com/archive", + "https://newsletter.banklesshq.com/archive", + "https://defislate.substack.com/archive", + ] + + threads = len(urls) + newsletters = [] + with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor: + for newsletter in executor.map(scrape_substack, urls): + try: + newsletters.append(pd.DataFrame(newsletter)) + except KeyError as e: + print(e, "\n") + continue + + df = pd.concat(newsletters, ignore_index=True) + df.columns = ["Title", "Link", "Date"] + + df["Title"] = df["Title"].apply(lambda x: "".join(i for i in x if ord(i) < 128)) + df["Date"] = df["Date"].apply( + lambda x: parser.parse(x).strftime("%Y-%m-%d %H:%M:%S") + ) + df["Title"] = df["Title"].apply( + lambda x: "\n".join(textwrap.wrap(x, width=50)) if isinstance(x, str) else x + ) + return ( + df[["Title", "Date", "Link"]] + .sort_values(by="Date", ascending=False) + .reset_index(drop="index") + ) diff --git a/gamestonk_terminal/cryptocurrency/defi/substack_view.py b/gamestonk_terminal/cryptocurrency/defi/substack_view.py new file mode 100644 index 00000000000..97732599919 --- /dev/null +++ b/gamestonk_terminal/cryptocurrency/defi/substack_view.py @@ -0,0 +1,46 @@ +"""Substack View""" +__docformat__ = "numpy" + + +import os +from tabulate import tabulate +from gamestonk_terminal.cryptocurrency.defi import substack_model +from gamestonk_terminal.helper_funcs import export_data +from gamestonk_terminal import feature_flags as gtff + + +def display_newsletters(top: int = 10, export: str = "") -> None: + """Display DeFi related substack newsletters. + [Source: substack.com] + + Parameters + ---------- + top: int + Number of records to display + export : str + Export dataframe data to csv,json,xlsx file + """ + + df = substack_model.get_newsletters() + df_data = df.copy() + + if gtff.USE_TABULATE_DF: + print( + tabulate( + df.head(top), + headers=df.columns, + floatfmt=".0f", + showindex=False, + tablefmt="fancy_grid", + ), + "\n", + ) + else: + print(df.to_string, "\n") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "newsletter", + df_data, + ) diff --git a/gamestonk_terminal/cryptocurrency/onchain/whale_alert_model.py b/gamestonk_terminal/cryptocurrency/onchain/whale_alert_model.py index 06554e95b65..a8a95e58377 100644 --- a/gamestonk_terminal/cryptocurrency/onchain/whale_alert_model.py +++ b/gamestonk_terminal/cryptocurrency/onchain/whale_alert_model.py @@ -63,8 +63,8 @@ def get_whales_transactions(min_value: int = 800000, limit: int = 100) -> pd.Dat Crypto wales transactions """ - min_value = 800000 if min_value > 800000 else min_value - limit = 100 if limit > 100 else limit + min_value = max(min_value, 800000) + limit = max(limit, 100) params = {"limit": limit, "min_value": min_value} diff --git a/website/content/cryptocurrency/defi/_index.md b/website/content/cryptocurrency/defi/_index.md new file mode 100644 index 00000000000..093e02e215d --- /dev/null +++ b/website/content/cryptocurrency/defi/_index.md @@ -0,0 +1,5 @@ +--- +geekdocCollapseSection: true +--- + +{{< toc-tree >}} diff --git a/website/content/cryptocurrency/defi/borrow/_index.md b/website/content/cryptocurrency/defi/borrow/_index.md new file mode 100644 index 00000000000..f684efea534 --- /dev/null +++ b/website/content/cryptocurrency/defi/borrow/_index.md @@ -0,0 +1,16 @@ +``` +usage: borrow [-t TOP] [--current] [--export {csv,json,xlsx}] [-h] +``` + +Display DeFi borrow rates. [Source: https://defirate.com/] + +``` +optional arguments: + -t TOP, --top TOP top N number records (default: 10) + --current Show Current Borrow Rates or Last 30 Days Average + (default: True) + --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/content/cryptocurrency/defi/dpi/_index.md b/website/content/cryptocurrency/defi/dpi/_index.md new file mode 100644 index 00000000000..de07cf6e306 --- /dev/null +++ b/website/content/cryptocurrency/defi/dpi/_index.md @@ -0,0 +1,19 @@ +``` +usage: dpi [-t TOP] [-s {Rank,Name,Chain,Category,TVL,Change_1D}] [--descend] + [--export {csv,json,xlsx}] [-h] +``` + +Displays DeFi Pulse crypto protocols. [Source: https://defipulse.com/] + +``` +optional arguments: + -t TOP, --top TOP top N number records (default: 15) + -s {Rank,Name,Chain,Category,TVL,Change_1D}, --sort {Rank,Name,Chain,Category,TVL,Change_1D} + Sort by given column. Default: Rank (default: Rank) + --descend Flag to sort in descending order (lowest first) + (default: True) + --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/content/cryptocurrency/defi/funding/_index.md b/website/content/cryptocurrency/defi/funding/_index.md new file mode 100644 index 00000000000..00c549a15c4 --- /dev/null +++ b/website/content/cryptocurrency/defi/funding/_index.md @@ -0,0 +1,16 @@ +``` +usage: funding [-t TOP] [--current] [--export {csv,json,xlsx}] [-h] +``` + +Display Funding rates. [Source: https://defirate.com/] + +``` +optional arguments: + -t TOP, --top TOP top N number records (default: 10) + --current Show Current Funding Rates or Last 30 Days Average + (default: True) + --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/content/cryptocurrency/defi/lending/_index.md b/website/content/cryptocurrency/defi/lending/_index.md new file mode 100644 index 00000000000..fc4a2d7048b --- /dev/null +++ b/website/content/cryptocurrency/defi/lending/_index.md @@ -0,0 +1,16 @@ +``` +usage: lending [-t TOP] [--current] [--export {csv,json,xlsx}] [-h] +``` + +Display DeFi lending rates. [Source: https://defirate.com/] + +``` +optional arguments: + -t TOP, --top TOP top N number records (default: 15) + --current Show Current Lending Rates or Last 30 Days Average + (default: True) + --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/content/cryptocurrency/defi/llama/_index.md b/website/content/cryptocurrency/defi/llama/_index.md new file mode 100644 index 00000000000..70a17e26e18 --- /dev/null +++ b/website/content/cryptocurrency/defi/llama/_index.md @@ -0,0 +1,21 @@ +``` +usage: llama [-t TOP] + [-s {tvl,symbol,category,chains,change_1h,change_1d,change_7d,tvl}] + [--descend] [--desc] [--export {csv,json,xlsx}] [-h] +``` +Display information about listed DeFi Protocols on DeFi Llama. [Source:https://docs.llama.fi/api] + +``` +optional arguments: + -t TOP, --top TOP top N number records (default: 10) + -s {tvl,symbol,category,chains,change_1h,change_1d,change_7d,tvl}, --sort {tvl,symbol,category,chains,change_1h,change_1d,change_7d,tvl} + Sort by given column. Default: tvl (default: tvl) + --descend Flag to sort in descending order (lowest first) + (default: False) + --desc Flag to display description of protocol (default: + False) + --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/content/cryptocurrency/defi/newsletter/_index.md b/website/content/cryptocurrency/defi/newsletter/_index.md new file mode 100644 index 00000000000..3abbc2e4849 --- /dev/null +++ b/website/content/cryptocurrency/defi/newsletter/_index.md @@ -0,0 +1,14 @@ +``` +usage: newsletter [-t TOP] [--export {csv,json,xlsx}] [-h] +``` + +Display DeFi related substack newsletters. [Source: substack.com] + +``` +optional arguments: + -t TOP, --top TOP top N number records (default: 10) + --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/content/cryptocurrency/defi/tvl/_index.md b/website/content/cryptocurrency/defi/tvl/_index.md new file mode 100644 index 00000000000..37d3de47357 --- /dev/null +++ b/website/content/cryptocurrency/defi/tvl/_index.md @@ -0,0 +1,16 @@ +``` +usage: tvl [-t TOP] [--export {csv,json,xlsx}] [-h] +``` + + +Displays historical values of the total sum of TVLs from all listed protocols. +[Source: https://docs.llama.fi/api] + +``` +optional arguments: + -t TOP, --top TOP top N number records (default: 10) + --export {csv,json,xlsx} + Export dataframe data to csv,json,xlsx file (default: + ) + -h, --help show this help message (default: False) +```