├── moonbag ├── __init__.py ├── discover │ ├── __init__.py │ ├── defi │ │ ├── __init__.py │ │ ├── utils.py │ │ ├── pulse.py │ │ ├── llama.py │ │ └── graph.py │ ├── airdrop │ │ ├── __init__.py │ │ └── menu.py │ ├── others │ │ ├── __init__.py │ │ ├── fng.py │ │ ├── funding.py │ │ ├── fourchan.py │ │ ├── wales.py │ │ └── cryptopanic.py │ ├── reddit_client │ │ ├── __init__.py │ │ ├── _client.py │ │ └── reddit.py │ ├── README.md │ └── menu.py ├── gecko │ ├── __init__.py │ ├── utils.py │ ├── README.md │ ├── overview_menu.py │ └── coin_menu.py ├── onchain │ ├── __init__.py │ ├── ethereum │ │ ├── __init__.py │ │ ├── utils.py │ │ ├── _client.py │ │ ├── eth.py │ │ └── menu.py │ ├── terraluna │ │ ├── __init__.py │ │ ├── _client.py │ │ ├── terra.py │ │ └── menu.py │ └── README.md ├── paprika │ ├── __init__.py │ ├── README.md │ ├── _client.py │ ├── coinpaprika.py │ └── menu.py ├── tests │ ├── __init__.py │ └── utils │ │ ├── __init__.py │ │ ├── test_cryptocompare.py │ │ ├── test_common_utils.py │ │ └── test_gecko_utils.py ├── cryptocompare │ ├── __init__.py │ ├── utils.py │ ├── README.md │ ├── _client.py │ └── cryptocomp.py └── common │ ├── keys.py │ ├── __init__.py │ └── utils.py ├── logo.png ├── .coveragerc ├── Makefile ├── .gitignore ├── pyproject.toml ├── requirements.txt ├── README.md ├── moon.py └── .pylintrc /moonbag/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /moonbag/discover/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /moonbag/gecko/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /moonbag/onchain/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /moonbag/paprika/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /moonbag/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /moonbag/cryptocompare/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /moonbag/discover/defi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /moonbag/tests/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /moonbag/discover/airdrop/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /moonbag/discover/others/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /moonbag/onchain/ethereum/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /moonbag/onchain/terraluna/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /moonbag/discover/reddit_client/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakubPluta/moonbag/HEAD/logo.png -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | tests/* 4 | /myenv/* 5 | moonbag/tests/* 6 | *__init__.py* 7 | *keys.py* 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | cov: 2 | pytest --cov=moonbag --cov-config=.coveragerc 3 | tests: 4 | pytest -vv moonbag/tests 5 | test-utils: 6 | pytest -vv moonbag\tests\utils 7 | -------------------------------------------------------------------------------- /moonbag/discover/defi/utils.py: -------------------------------------------------------------------------------- 1 | def get_slug_mappings(lst: list): 2 | mappings = {} 3 | for item in lst: 4 | if item.get("slug") is not None: 5 | mappings[item["slug"].lower()] = item.get("name").lower() 6 | else: 7 | mappings[item["name"].lower()] = item.get("name").lower() 8 | return mappings 9 | -------------------------------------------------------------------------------- /moonbag/tests/utils/test_cryptocompare.py: -------------------------------------------------------------------------------- 1 | from moonbag.cryptocompare.utils import ( 2 | get_closes_matches_by_name, 3 | get_closes_matches_by_symbol, 4 | create_dct_mapping_from_df, 5 | ) 6 | import pandas as pd 7 | 8 | 9 | def test_create_dct_mapping_for_df(): 10 | df = pd.DataFrame({"col1": ["one", "two"], "col2": ["abc", "cde"]}) 11 | assert create_dct_mapping_from_df(df, col1="col1", col2="col2") == { 12 | "one": "abc", 13 | "two": "cde", 14 | } 15 | -------------------------------------------------------------------------------- /moonbag/common/keys.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | import os 3 | 4 | load_dotenv() 5 | 6 | 7 | WALES_API_KEY = os.getenv("WALES_API_KEY") # or "Enter your key" 8 | CC_API_KEY = os.getenv("CC_API_KEY") # or "Enter your key" 9 | 10 | REDDIT_CLIENT_ID = os.getenv("REDDIT_CLIENT_ID") # or "Enter your client id" 11 | REDDIT_CLIENT_SECRET = os.getenv( 12 | "REDDIT_CLIENT_SECRET" 13 | ) # or "Enter your client secret" 14 | REDDIT_USER_AGENT = os.getenv("REDDIT_USER_AGENT") # "Enter your client user agent" 15 | CRYPTO_PANIC_API = os.getenv("CRYPTO_PANIC_API") # or "Enter your client user agent" 16 | BIT_QUERY_API = os.getenv("BIT_QUERY_API") -------------------------------------------------------------------------------- /moonbag/discover/others/fng.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import requests 3 | import datetime 4 | 5 | # https://github.com/RaidasGrisk/reddit-coin-app/blob/master/app/main.py 6 | # https://github.com/ssp0929/crypto-trend-tracker/tree/master/input_data 7 | 8 | 9 | def get_fng(limit=60): 10 | if not isinstance(limit, int) and limit < 1: 11 | limit = 30 12 | url = f"https://api.alternative.me/fng/?limit={limit}" 13 | data = requests.get(url=url).json()["data"] 14 | df = pd.DataFrame(data)[["timestamp", "value_classification", "value"]] 15 | df["timestamp"] = df["timestamp"].apply( 16 | lambda x: datetime.datetime.fromtimestamp((int(x))) 17 | ) 18 | return df 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | *.idea 9 | __.idea__/ 10 | 11 | # Distribution / packaging 12 | bin/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | /myenv/*.* 26 | # Installer logs 27 | pip-log.txt 28 | pip-delete-this-directory.txt 29 | 30 | # Unit test / coverage reports 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | -------------------------------------------------------------------------------- /moonbag/onchain/ethereum/utils.py: -------------------------------------------------------------------------------- 1 | import numbers 2 | import logging 3 | 4 | 5 | def converter(num): 6 | if isinstance(num, numbers.Number): 7 | "{:.14f}".format(num) 8 | 9 | 10 | def split_cols(x): 11 | if "." in x: 12 | p1, p2 = x.split(".") 13 | p2 = manual_replace(p2, p2[0].upper(), 0) 14 | return p1 + p2 15 | return x 16 | 17 | 18 | def manual_replace(s, char, index): 19 | return s[:index] + char + s[index + 1 :] 20 | 21 | 22 | def enrich_social_media(dct: dict): 23 | social_media = { 24 | "twitter": "https://www.twitter.com/", 25 | "reddit": "https://www.reddit.com/r/", 26 | "coingecko": "https://www.coingecko.com/en/coins/", 27 | } 28 | try: 29 | for k, v in social_media.items(): 30 | if k in dct: 31 | dct[k] = v + dct[k] 32 | except Exception as e: 33 | logging.error(e) 34 | -------------------------------------------------------------------------------- /moonbag/common/__init__.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from tabulate import tabulate 3 | 4 | 5 | MOON = "(moon)" 6 | 7 | LOGO = """ 8 | .:::::/|::::. 9 | ::::::/ V|::::: 10 | ::::::/' |:::::: 11 | ::::<_, (:::::: 12 | :::::| \:::: 13 | ::/ \:: 14 | 15 | Welcome in MoonBag Terminal! 16 | """ 17 | 18 | 19 | def print_table(df: pd.DataFrame, floatfmt=".4f", tablefmt="psql"): # pragma: no cover 20 | if not isinstance(df, pd.DataFrame): 21 | raise TypeError("Please use data frame as an input!") 22 | print( 23 | tabulate( 24 | df, 25 | headers=df.columns, 26 | floatfmt=floatfmt, 27 | showindex=False, 28 | tablefmt=tablefmt, 29 | ) 30 | ) 31 | print("") 32 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "moonbag" 3 | version = "0.1.0" 4 | description = "Moonbag is command line application that helps to explore crypto trends, coin prices, exchanges and many others." 5 | authors = ["jakub "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.9" 10 | pycoingecko = "^2.0.0" 11 | requests = "^2.25.1" 12 | pandas = "^1.2.4" 13 | python-dotenv = "^0.17.1" 14 | numpy = "^1.20.3" 15 | retry = "^0.9.2" 16 | lxml = "^4.6.3" 17 | bs4 = "^0.0.1" 18 | cachetools = "^4.2.2" 19 | tabulate = "^0.8.9" 20 | praw = "^7.2.0" 21 | psaw = "^0.1.0" 22 | asyncpraw = "^7.2.0" 23 | aPRAW = "^0.6.8-alpha.0" 24 | pytest-cov = "^2.12.1" 25 | 26 | [tool.poetry.dev-dependencies] 27 | 28 | [build-system] 29 | requires = ["poetry-core>=1.0.0"] 30 | build-backend = "poetry.core.masonry.api" 31 | 32 | [tool.black] 33 | line-length = 88 34 | target-version = ['py37'] 35 | include = '\.pyi?$' 36 | -------------------------------------------------------------------------------- /moonbag/discover/defi/pulse.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | import pandas as pd 4 | from moonbag.common import print_table 5 | 6 | 7 | def get_dpi(): 8 | req = requests.get("https://defipulse.com/") 9 | result = req.content.decode("utf8") 10 | soup = BeautifulSoup(result, features="lxml") 11 | table = soup.find("tbody").find_all("tr") 12 | list_of_records = [] 13 | for row in table: 14 | row_elements = [] 15 | for element in row.find_all("td"): 16 | text = element.text 17 | row_elements.append(text) 18 | list_of_records.append(row_elements) 19 | df = pd.DataFrame( 20 | list_of_records, 21 | columns=[ 22 | "x", 23 | "Defi Pulse Rank", 24 | "Name", 25 | "Chain", 26 | "Category", 27 | "Locked (USD)", 28 | "1 Day % Change", 29 | ], 30 | ) 31 | df.drop("x", axis=1, inplace=True) 32 | return df 33 | -------------------------------------------------------------------------------- /moonbag/discover/others/funding.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import requests 3 | from bs4 import BeautifulSoup 4 | 5 | 6 | def get_funding_rates(current=True): 7 | url = "https://defirate.com/funding/" 8 | req = requests.get(url) 9 | soup = BeautifulSoup(req.text, features="lxml") 10 | if current: 11 | print("\nDisplaying current Funding Rates\n") 12 | table = soup.find("div", class_="table-container").find("table") 13 | else: 14 | print("\nDisplaying 30 day average Funding Rates\n") 15 | table = soup.find("div", class_="table-container table-hidden").find("table") 16 | items = [] 17 | first_row = table.find("thead").text.strip().split() 18 | headers = [r for r in first_row if r != "Trade"] 19 | headers.insert(0, "Symbol") 20 | for i in table.find_all("td"): 21 | items.append(i.text.strip()) 22 | fundings = [items[i : i + 5] for i in range(0, len(items), 5)] 23 | df = pd.DataFrame(columns=headers, data=fundings) 24 | return df 25 | -------------------------------------------------------------------------------- /moonbag/discover/reddit_client/_client.py: -------------------------------------------------------------------------------- 1 | import praw 2 | import requests 3 | from functools import lru_cache 4 | from moonbag.common import keys 5 | from psaw import PushshiftAPI 6 | 7 | 8 | class RedditClient: 9 | def __init__(self): 10 | try: 11 | self.client = praw.Reddit( 12 | client_id=keys.REDDIT_CLIENT_ID, 13 | client_secret=keys.REDDIT_CLIENT_SECRET, 14 | user_agent=keys.REDDIT_USER_AGENT, 15 | ) 16 | except Exception: 17 | self.client = None 18 | self.psaw = PushshiftAPI() 19 | 20 | @lru_cache(maxsize=256) 21 | def _get_subbreddit(self, subreddit): 22 | return self.client.subreddit(subreddit) 23 | 24 | @lru_cache(maxsize=256) 25 | def _get_submission(self, submission_id): 26 | return self.client.submission(submission_id) 27 | 28 | @lru_cache(maxsize=256) 29 | def _get_comment(self, comment_id): 30 | return self.client.comment(comment_id) 31 | 32 | def _search_psaw_data(self, data_type, **kwargs): 33 | base_url = f"https://api.pushshift.io/reddit/{data_type}/search" 34 | payload = kwargs 35 | request = requests.get(base_url, params=payload) 36 | return request.json() 37 | -------------------------------------------------------------------------------- /moonbag/onchain/terraluna/_client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from retry import retry 3 | from requests.adapters import HTTPAdapter 4 | 5 | 6 | class TerraClient: 7 | BASE_URL = "https://fcd.terra.dev/" 8 | 9 | def __init__(self): 10 | self.header = {"Accept": "application/json", "User-Agent": "moonbag"} 11 | self.s = requests.Session() 12 | self.s.mount(self.BASE_URL, HTTPAdapter(max_retries=5)) 13 | 14 | @retry(tries=2, max_delay=5) 15 | def _make_request(self, endpoint, payload=None, **kwargs): 16 | url = self.BASE_URL + endpoint 17 | if payload is None: 18 | payload = {} 19 | if kwargs: 20 | payload.update(kwargs) 21 | return requests.get(url, params=payload).json() 22 | 23 | def _get_tx(self, address): 24 | return self._make_request(f"txs/{address}") 25 | 26 | def _get_account(self, address): 27 | return self._make_request(f"auth/accounts/{address}") 28 | 29 | def _get_staking_info(self): 30 | return self._make_request("staking/pool") 31 | 32 | def _get_coins_supply(self): 33 | return self._make_request("supply/total") 34 | 35 | def _get_all_validators(self): 36 | return self._make_request("staking/validators") 37 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.9.3 2 | bs4==0.0.1 3 | cachetools==4.2.2; python_version >= "3.5" and python_version < "4.0" 4 | certifi==2020.12.5; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" 5 | chardet==4.0.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" 6 | decorator==5.0.9; python_version >= "3.5" 7 | idna==2.10; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" 8 | lxml==4.6.3; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") 9 | numpy==1.20.3; python_version >= "3.7" 10 | pandas==1.2.4; python_full_version >= "3.7.1" 11 | py==1.10.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" 12 | pycoingecko==2.0.0 13 | python-dateutil==2.8.1; python_full_version >= "3.7.1" 14 | python-dotenv==0.17.1 15 | pytz==2021.1; python_full_version >= "3.7.1" 16 | requests==2.25.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") 17 | retry==0.9.2 18 | six==1.16.0; python_full_version >= "3.7.1" 19 | soupsieve==2.2.1; python_version >= "3.6" 20 | urllib3==1.26.4; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" 21 | -------------------------------------------------------------------------------- /moonbag/cryptocompare/utils.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import difflib 3 | import logging 4 | import argparse 5 | 6 | 7 | logger = logging.getLogger("cryptocompare-utils") 8 | 9 | # TODO Make those algorithms better for name 10 | def get_closes_matches_by_name(name: str, coins: dict): # pragma: no cover 11 | sim = difflib.get_close_matches(name, list(coins.values()), 10, cutoff=0.3) 12 | res = {} 13 | try: 14 | for s in sim: 15 | for k, v in coins.items(): 16 | if s == v: 17 | res[k] = v 18 | else: 19 | continue 20 | except TypeError as e: 21 | logger.log(2, e) 22 | return res 23 | 24 | 25 | def get_closes_matches_by_symbol(symbol: str, coins: dict): # pragma: no cover 26 | sim = difflib.get_close_matches(symbol.upper(), list(coins.keys()), 10, cutoff=0.5) 27 | try: 28 | res = {s: coins.get(s) for s in sim} 29 | except TypeError as e: 30 | logger.log(2, e) 31 | res = {} 32 | return res 33 | 34 | 35 | def create_dct_mapping_from_df(df, col1, col2): 36 | return dict(zip(df[col1], df[col2])) 37 | 38 | 39 | def print_no_api_key_msg(): # pragma: no cover 40 | print( 41 | "\n\nYou didn't pass API key for CryptoComapre. You can't use that section.\n" 42 | "To do that please visit https://min-api.cryptocompare.com/ and get your free key\n" 43 | "Then go to moonbag.common.keys and add your key to variable CC_API_KEY\n" 44 | "CC_API_KEY = and restart program\n" 45 | ) 46 | -------------------------------------------------------------------------------- /moonbag/discover/others/fourchan.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import datetime 3 | import textwrap 4 | import requests 5 | import numpy as np 6 | import re 7 | 8 | 9 | def html_clearer(text): 10 | regex = re.compile("<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});") 11 | return re.sub(regex, "", text) 12 | 13 | 14 | def get_last_4chans(): 15 | r = requests.get("https://a.4cdn.org/biz/catalog.json").json() 16 | data = [] 17 | for item in r: 18 | threads = item["threads"] 19 | for i, t in enumerate(threads): 20 | res = { 21 | "sub": t.get("sub") or "", 22 | "com": t.get("com"), 23 | "replies": t.get("replies"), 24 | "last_modified": datetime.datetime.fromtimestamp( 25 | t.get("last_modified") 26 | ), 27 | } 28 | data.append(res) 29 | 30 | df = pd.DataFrame(data).sort_values(by="last_modified", ascending=False) 31 | df["com"] = df["com"].apply(lambda x: html_clearer(str(x))) 32 | df["sub"] = df["sub"].apply(lambda x: html_clearer(str(x))) 33 | df["com"] = df["com"].apply( 34 | lambda x: "\n".join(textwrap.wrap(x, width=86)) if isinstance(x, str) else x 35 | ) 36 | df["sub"] = df["sub"].apply( 37 | lambda x: "\n".join(textwrap.wrap(x, width=26)) if isinstance(x, str) else x 38 | ) 39 | df["com"] = df["com"].apply(lambda x: "" if x in ["None", None, np.NaN] else x) 40 | df = df[df["last_modified"] >= datetime.datetime.now() - datetime.timedelta(days=7)] 41 | return df[["last_modified", "replies", "sub", "com"]].sort_values( 42 | by="last_modified", ascending=False 43 | ) 44 | -------------------------------------------------------------------------------- /moonbag/tests/utils/test_common_utils.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import datetime 3 | from moonbag.common.utils import ( 4 | formatter, 5 | underscores_to_newline_replace, 6 | wrap_headers_in_dataframe, 7 | created_date, 8 | MoonParser, 9 | BASE_PARSER_ARGUMENTS, 10 | ) 11 | 12 | 13 | def test_formatter(): 14 | assert formatter(5) == "5.00" 15 | assert formatter(0.2) == "0.200000" 16 | assert formatter(5555) == "5555.00" 17 | assert formatter("abc") == "abc" 18 | 19 | 20 | def test_underscores_to_new_line(): 21 | cols = ["column_number_one", "column_number_two"] 22 | assert underscores_to_newline_replace(cols) == [ 23 | "column number\none", 24 | "column number\ntwo", 25 | ] 26 | assert underscores_to_newline_replace(["column", "col_one"]) == [ 27 | "column", 28 | "col one", 29 | ] 30 | 31 | 32 | def test_wrap_headers_in_df(): 33 | df = pd.DataFrame( 34 | columns=["column_number_one", "column_number_two"], data=[[0, 2], [3, 4]] 35 | ) 36 | assert wrap_headers_in_dataframe(df, 15, "_") == [ 37 | "column number\none", 38 | "column number\ntwo", 39 | ] 40 | 41 | 42 | def test_created_date(): 43 | dt = 1622844000 44 | assert created_date(dt) == datetime.datetime(2021, 6, 5, 0, 0, 0) 45 | 46 | 47 | def test_moon_parser(): 48 | moon = MoonParser() 49 | dct = BASE_PARSER_ARGUMENTS["coin"] 50 | r = moon._modify_default_dict_of_arguments( 51 | dct=dct, 52 | dest="name", 53 | help="this is test name arg", 54 | type=str, 55 | fake=1, 56 | ) 57 | assert r == { 58 | "help": "this is test name arg", 59 | "dest": "name", 60 | "required": True, 61 | "default": "ETH", 62 | "type": str, 63 | } 64 | -------------------------------------------------------------------------------- /moonbag/discover/others/wales.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | from moonbag.common.keys import WALES_API_KEY 4 | import pandas as pd 5 | import textwrap 6 | 7 | 8 | def _get_wales_stats(min_value=1000000): 9 | req = f"https://api.whale-alert.io/v1/transactions?api_key={WALES_API_KEY}" 10 | params = {"min_value": min_value, "start": int(time.time()) - 3000} 11 | return requests.get(url=req, params=params).json() 12 | 13 | 14 | def get_wales_stats(): 15 | if not WALES_API_KEY: 16 | print( 17 | "You need to provide whale-alert api key.\n Please visit: https://api.whale-alert.io/v1 to get your key\n" 18 | ) 19 | return pd.DataFrame() 20 | 21 | data = _get_wales_stats() 22 | if data == {"result": "error", "message": "invalid api_key"}: 23 | print("Wrong API KEY.") 24 | return pd.DataFrame() 25 | data = pd.json_normalize(data["transactions"]).sort_values( 26 | "timestamp", ascending=False 27 | ) 28 | data.drop( 29 | [ 30 | "id", 31 | "transaction_count", 32 | "from.owner_type", 33 | "to.owner_type", 34 | "to.owner", 35 | "from.owner", 36 | "transaction_type", 37 | "hash", 38 | ], 39 | axis=1, 40 | inplace=True, 41 | ) 42 | data["timestamp"] = pd.to_datetime(data["timestamp"], unit="s") 43 | data.columns = [col.replace(".address", "") for col in data.columns] 44 | data["to"] = data["to"].apply( 45 | lambda x: "\n".join(textwrap.wrap(x, width=46)) if isinstance(x, str) else x 46 | ) 47 | data["from"] = data["from"].apply( 48 | lambda x: "\n".join(textwrap.wrap(x, width=46)) if isinstance(x, str) else x 49 | ) 50 | return data.sort_values(by="amount_usd", ascending=False) 51 | -------------------------------------------------------------------------------- /moonbag/discover/airdrop/menu.py: -------------------------------------------------------------------------------- 1 | import webbrowser 2 | import argparse 3 | import logging 4 | from moonbag.common import LOGO, MOON, print_table 5 | import pandas as pd 6 | from argparse import ArgumentError 7 | import os 8 | import sys 9 | 10 | 11 | def airdrops(): 12 | webbrowser.open("https://airdrops.io/") 13 | 14 | 15 | def airdrop_alerts(): 16 | webbrowser.open("https://airdropalert.com/") 17 | 18 | 19 | def airdrops_cmc(): 20 | webbrowser.open("https://coinmarketcap.com/en/airdrop/") 21 | 22 | 23 | def airdrop_view(args): 24 | 25 | parser = argparse.ArgumentParser(prog="airdrop", add_help=True) 26 | parser.add_argument( 27 | "-w", 28 | "--web", 29 | choices=["cmc", "airdrops", "airdropalert"], 30 | default="airdrops", 31 | required=False, 32 | dest="airdrop", 33 | ) 34 | parsy, others = parser.parse_known_args(args) 35 | if parsy.airdrop == "cmc": 36 | airdrops_cmc() 37 | elif parsy.airdrop == "airdrops": 38 | airdrops() 39 | elif parsy.airdrop == "airdropalert": 40 | airdrop_alerts() 41 | 42 | 43 | def main(): 44 | choices = ["airdrop", "quit", "q", "r"] 45 | if sys.platform == "win32": 46 | os.system("") 47 | 48 | parser = argparse.ArgumentParser(prog="cmc", add_help=False) 49 | parser.add_argument("cmd", choices=choices) 50 | 51 | print(LOGO) 52 | while True: 53 | an_input = input(f"{MOON}> ") 54 | try: 55 | parsy, others = parser.parse_known_args(an_input.split()) 56 | cmd = parsy.cmd 57 | 58 | if cmd in ["exit", "quit", "q"]: 59 | return False 60 | elif cmd == "r": 61 | return True 62 | 63 | if cmd == "airdrop": 64 | airdrop_view(others) 65 | 66 | except ArgumentError: 67 | print("The command selected doesn't exist") 68 | print("\n") 69 | continue 70 | 71 | except SystemExit: 72 | print("\n") 73 | continue 74 | 75 | 76 | if __name__ == "__main__": 77 | main() 78 | -------------------------------------------------------------------------------- /moonbag/discover/README.md: -------------------------------------------------------------------------------- 1 | ## How to use discover menu 2 | discover menu contains different features that helps to explore crypto world. 3 | 4 | ### 1. top_subs 5 | find top submissions on most popular crypto subreddits [Reddit] 6 | ``` 7 | top_subs 8 | ``` 9 | ### 2. search_subs 10 | show given subreddit for most popular submissions in last days [Reddit] 11 | required argument 12 | * -s, --subreddit [subreddit name] 13 | ``` 14 | search_subs -s CryptoMoonShots 15 | ``` 16 | ### 3. search_reddit 17 | search on reddit with you own query [Reddit] 18 | required argument 19 | * -q, --query [your query] 20 | ``` 21 | search_reddit -q ethereum 22 | ``` 23 | ### 4. dpi 24 | show defi pulse index protocols [DefiPulse] 25 | ``` 26 | dpi 27 | ``` 28 | ### 5. defi 29 | show DeFi protocols stats [LLama] 30 | ``` 31 | defi 32 | ``` 33 | ### 6. fng 34 | show fear and greed index for last n days [Fear and greed] 35 | required argument 36 | * -n, --limit [last N days] 37 | ``` 38 | fng -n 30 39 | ``` 40 | ### 7. news 41 | show last crypto news [Cryptopanic] 42 | ``` 43 | news 44 | ``` 45 | ### 8. fundings 46 | show crypto funding rates [Defirate] 47 | ``` 48 | fundings 49 | ``` 50 | ### 9. 4chan 51 | show last 4chan submissions [4chan] 52 | ``` 53 | 4chan 54 | ``` 55 | ### 10. wales 56 | show wales transactions [Whale-Alert] 57 | ``` 58 | wales 59 | ``` 60 | ### 11. uni_pairs 61 | show recently added pairs on UniSwap [TheGraph] 62 | optional arguments: 63 | -d, --d, --days [last n of days] 64 | -v, --v, --volume [min volume] 65 | -l, --l, --liquid [min liquidity] 66 | -t --t --txs [min transactions] 67 | ``` 68 | uni_pairs -d 20 -v 1000 -l 1000 -t 100 69 | ``` 70 | ### 10. uni_tokens 71 | show tokens available on UniSwap [TheGraph] 72 | optional arguments: 73 | -s, --skip [you can only query 1000 records.] 74 | ``` 75 | # first page 76 | uni_tokens 77 | # 2nd page 78 | uni_tokens -s 1000 79 | # 5th page 80 | uni_tokens -s 5000 81 | ``` 82 | ### 11. uni_swaps 83 | show recent coins swaps on on UniSwap [TheGraph] 84 | ``` 85 | uni_swaps 86 | ``` 87 | ### 12. dex_trades 88 | show stats about DeFi dex trades [TheGraph] 89 | ``` 90 | dex_trades 91 | ``` 92 | ### 13. compound 93 | show available compound markets [TheGraph] 94 | ``` 95 | compound 96 | ``` -------------------------------------------------------------------------------- /moonbag/onchain/ethereum/_client.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import json 3 | import requests 4 | import logging 5 | from moonbag.onchain.ethereum.utils import ( 6 | manual_replace, 7 | enrich_social_media, 8 | split_cols, 9 | ) 10 | from datetime import datetime 11 | 12 | 13 | class EthplorerClient: 14 | def __init__(self, api_key=None): 15 | self.api_key = api_key if api_key else "freekey" 16 | self.api_query = f"?apiKey={self.api_key}" 17 | 18 | @staticmethod 19 | def _request_call(x): 20 | response = requests.request("GET", x) 21 | return json.loads(response.text) 22 | 23 | def _get_address_info(self, address): 24 | url = f"https://api.ethplorer.io/getAddressInfo/{address}{self.api_query}" 25 | return self._request_call(url) 26 | 27 | def _get_token_info(self, address): 28 | url = f"https://api.ethplorer.io/getTokenInfo/{address}{self.api_query}" 29 | return self._request_call(url) 30 | 31 | def _get_tx_info(self, tx_hash): 32 | url = f"https://api.ethplorer.io/getTxInfo/{tx_hash}{self.api_query}" 33 | return self._request_call(url) 34 | 35 | def _get_token_history(self, address): 36 | url = f"https://api.ethplorer.io/getTokenHistory/{address}{self.api_query}&limit=1000" 37 | return self._request_call(url) 38 | 39 | def _get_address_history(self, address): 40 | url = f"https://api.ethplorer.io/getAddressHistory/{address}{self.api_query}&limit=1000" 41 | return self._request_call(url) 42 | 43 | def _get_address_transactions(self, address): 44 | url = ( 45 | f"https://api.ethplorer.io/getAddressTransactions/{address}{self.api_query}" 46 | ) 47 | return self._request_call(url) 48 | 49 | def _get_token_price_history_grouped(self, address): 50 | url = f"https://api.ethplorer.io/getTokenPriceHistoryGrouped/{address}{self.api_query}" 51 | return self._request_call(url) 52 | 53 | def _get_token_history_grouped(self, address): 54 | url = ( 55 | f"https://api.ethplorer.io/getTokenHistoryGrouped/{address}{self.api_query}" 56 | ) 57 | return self._request_call(url) 58 | 59 | def _get_top_token_holders(self, address): 60 | url = f"https://api.ethplorer.io/getTopTokenHolders/{address}{self.api_query}&limit=100" 61 | return self._request_call(url) 62 | 63 | def _get_top_tokens(self): 64 | url = f"https://api.ethplorer.io/getTopTokens/{self.api_query}" 65 | return self._request_call(url) 66 | -------------------------------------------------------------------------------- /moonbag/discover/others/cryptopanic.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pandas as pd 3 | import textwrap 4 | import requests 5 | from requests.adapters import HTTPAdapter 6 | from moonbag.common import print_table 7 | from moonbag.common.keys import CRYPTO_PANIC_API 8 | 9 | 10 | class CryptoPanic: 11 | BASE_URL = "https://cryptopanic.com/api/v1" 12 | 13 | def __init__(self): 14 | self.api_key = CRYPTO_PANIC_API or "" 15 | self.s = requests.Session() 16 | self.s.mount(self.BASE_URL, HTTPAdapter(max_retries=5)) 17 | 18 | @staticmethod 19 | def parse_post(post): 20 | return { 21 | "published_at": post.get("published_at"), 22 | "domain": post.get("domain"), 23 | "title": post.get("title"), 24 | "negative_votes": post["votes"].get("negative"), 25 | "positive_votes": post["votes"].get("positive"), 26 | } 27 | 28 | def _get_posts(self, kind="news"): 29 | if kind not in ["news", "media"]: 30 | kind = "news" 31 | 32 | results = [] 33 | 34 | url = f"{self.BASE_URL}/posts/?auth_token={self.api_key}" + f"&kind={kind}" 35 | print(f"Fetching page: 0") 36 | first_page = requests.get(url).json() 37 | 38 | if first_page == { 39 | "status": "Incomplete", 40 | "info": "Missing auth_token parameter", 41 | }: 42 | print(first_page) 43 | return None 44 | 45 | data, next_page = first_page["results"], first_page.get("next") 46 | 47 | for post in data: 48 | results.append(self.parse_post(post)) 49 | 50 | counter = 0 51 | while next_page: 52 | counter += 1 53 | print(f"Fetching page: {counter}") 54 | try: 55 | time.sleep(0.1) 56 | res = requests.get(next_page).json() 57 | for post in res["results"]: 58 | results.append(self.parse_post(post)) 59 | next_page = res.get("next") 60 | except Exception as e: 61 | print(e) 62 | 63 | return results 64 | 65 | def get_posts(self, kind="news"): 66 | """kind: news or media""" 67 | pst = self._get_posts(kind) 68 | if not pst: 69 | print( 70 | "You need to provide API Key to use this feature. \nPlease visit https://cryptopanic.com/developers/api/ and get your API KEY" 71 | ) 72 | df = pd.DataFrame(pst) 73 | df["title"] = df["title"].apply( 74 | lambda x: "\n".join(textwrap.wrap(x, width=66)) if isinstance(x, str) else x 75 | ) 76 | return df 77 | -------------------------------------------------------------------------------- /moonbag/paprika/README.md: -------------------------------------------------------------------------------- 1 | ## How to use CoinPaprika 2 | CoinPaprika has free api, so you don't need to generate eny api keys. 3 | 4 | ### 1. global_info 5 | show global info about crypto market [Coinpaprika] 6 | ``` 7 | global_info 8 | ``` 9 | ### 2. search 10 | try to find coin, exchange [Coinpaprika] 11 | arguments: 12 | * -q, --query [search query] 13 | ``` 14 | search -q uni 15 | ``` 16 | ### 3. coins_list 17 | show available coins: symbol and names [Coinpaprika] 18 | ``` 19 | coins 20 | ``` 21 | ### 4. coins_info 22 | show base information about cryptocurrencies [Coinpaprika] 23 | arguments: 24 | * -n, --limit [number of results] 25 | ``` 26 | coins_info -n 100 27 | ``` 28 | ### 5. coins_market 29 | show market information about cryptocurrencies [Coinpaprika] 30 | arguments: 31 | * -n, --limit [number of results] 32 | ``` 33 | coins_market -n 100 34 | ``` 35 | ### 6. exchanges_info 36 | show crypto exchanges information [Coinpaprika] 37 | arguments: 38 | * -n, --limit [number of results] 39 | ``` 40 | exchanges_info -n 100 41 | ``` 42 | ### 7. exchanges_market 43 | show crypto exchanges market information [Coinpaprika] 44 | arguments: 45 | * -n, --limit [number of results] 46 | ``` 47 | exchanges_market -n 100 48 | ``` 49 | ### 8. platforms 50 | show all platforms with smart contracts 51 | ``` 52 | platforms 53 | ``` 54 | ### 9. contracts 55 | show all contract platforms. Default: eth-ethereum. 56 | To find platform name use, "platforms" command 57 | arguments: 58 | * -p , --platform [platform id] 59 | ``` 60 | contracts -p eth-ethereum 61 | ``` 62 | 63 | ### 10. coin_exchanges 64 | show all exchanges for given coin. Use coin_id as input [Coinpaprika] 65 | For now you need to use coin_id like : eth-ethereum (to find all coins_ids use coins_list command) 66 | arguments: 67 | * -c , --coin [coin symbol] 68 | ``` 69 | coin_exchanges -c eth-ethereum 70 | ``` 71 | ### 11. coin_events 72 | show all event for given coin Use coin_id as input [Coinpaprika] 73 | For now you need to use coin_id like : eth-ethereum (to find all coins_ids use coins_list command) 74 | arguments: 75 | * -c , --coin [coin symbol] 76 | ``` 77 | coin_events -c eth-ethereum 78 | ``` 79 | ### 12. coin_twitter 80 | show twitter timeline for given coin. Use coin_id as input [Coinpaprika] 81 | For now you need to use coin_id like : eth-ethereum (to find all coins_ids use coins_list command) 82 | arguments: 83 | * -c , --coin [coin symbol] 84 | ``` 85 | coin_twitter -c eth-ethereum 86 | ``` 87 | ### 13. coin_ohlc 88 | show coin open-high-low-close prices data for last year. Use coin_id as input [Coinpaprika] 89 | For now you need to use coin_id like : eth-ethereum (to find all coins_ids use coins_list command) 90 | arguments: 91 | * -c , --coin [coin symbol] 92 | ``` 93 | coin_ohlc -c eth-ethereum 94 | ``` -------------------------------------------------------------------------------- /moonbag/discover/defi/llama.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import pandas as pd 3 | import cachetools.func 4 | from moonbag.common.utils import table_formatter 5 | from moonbag.discover.defi.utils import get_slug_mappings 6 | 7 | 8 | class LLama: 9 | URL = "https://api.llama.fi/" 10 | ENDPOINTS = {"protocols": "protocols", "protocol": "protocol/"} 11 | 12 | def __init__(self): 13 | self._symbols = get_slug_mappings(self._get_protocols()) 14 | 15 | @cachetools.func.ttl_cache(maxsize=128, ttl=10 * 60) 16 | def _get_protocols(self): 17 | resp = requests.get(self.URL + self.ENDPOINTS.get("protocols")) 18 | resp.raise_for_status() 19 | return resp.json() 20 | 21 | def get_protocols(self): 22 | df = pd.DataFrame.from_dict(self._get_protocols())[ 23 | [ 24 | "name", 25 | "symbol", 26 | # "address", 27 | "category", 28 | "chains", 29 | "change_1h", 30 | "change_1d", 31 | "change_7d", 32 | "tvl", 33 | "url", 34 | # "description", 35 | # "module", 36 | # "audits", 37 | # "gecko_id", 38 | # "cmcId", 39 | ] 40 | ].set_index("name") 41 | df["chains"] = df["chains"].apply(lambda x: ",".join(x)) 42 | return df 43 | 44 | @property 45 | def symbols(self): 46 | return self._symbols 47 | 48 | @cachetools.func.ttl_cache(maxsize=128, ttl=10 * 60) 49 | def _get_protocol(self, protocol: str): 50 | # https://api.llama.fi/protocol/:name 51 | protocol = str(protocol).lower() 52 | for slug, name in self.symbols.items(): 53 | if protocol == name: 54 | protocol = slug 55 | break 56 | elif protocol == slug: 57 | break 58 | else: 59 | raise ValueError( 60 | f"Wrong protocol name\nPlease chose protocol name from list\n{self.symbols}" 61 | ) 62 | 63 | resp = requests.get(self.URL + self.ENDPOINTS.get("protocol") + protocol) 64 | resp.raise_for_status() 65 | return resp.json() 66 | 67 | def get_protocol_info(self, protocol: str): 68 | data = self._get_protocol(protocol) 69 | for stat in ["tvl", "tokensInUsd"]: 70 | data.pop(stat) 71 | df = pd.json_normalize(data) 72 | df["chains"] = df["chains"].apply(lambda x: ",".join(x)) 73 | return df.T 74 | 75 | def get_protocol_total_value_locked(self, protocol: str): 76 | data = self._get_protocol(protocol).get("tvl") 77 | if not data: 78 | return pd.DataFrame() 79 | df = pd.json_normalize(data) 80 | df["date"] = pd.to_datetime(df["date"], unit="s") 81 | return df 82 | -------------------------------------------------------------------------------- /moonbag/onchain/README.md: -------------------------------------------------------------------------------- 1 | ## How to use on-chain data menu 2 | Currently there are two options to explore on-chain data. 3 | Moonbag supports ethereum explorer and terra explorer 4 | - ethereum - explore on-chain data for ethereum [Ethplorer] 5 | - terra - explore on-chain data for terra [TerraAPI] 6 | 7 | 8 | ## Ethereum on-chain data 9 | 10 | ### 1. token_info 11 | show info about erc20 token [Ethplorer] 12 | required argument 13 | * -a, --address [Token on-chain address] 14 | ``` 15 | token_info -a 0xa8b919680258d369114910511cc87595aec0be6d 16 | ``` 17 | ### 2. tx_info 18 | show info about transaction on ethereum blockchain [Ethplorer] 19 | required argument 20 | * -a, --address [transaction on-chain address] 21 | ``` 22 | tx_info -a 0x0836324289958ed2ed9e3b88f9538464ffa786358ada6ce956938213f9c30da2 23 | ``` 24 | ### 3. address_info 25 | show info about ethereum address [Ethplorer] 26 | required argument 27 | * -a, --address [on-chain address] 28 | ``` 29 | address_info -a 0xa8b919680258d369114910511cc87595aec0be6d 30 | ``` 31 | ### 4. address_tx 32 | show ethereum address transactions [Ethplorer] 33 | required argument 34 | * -a, --address [on-chain address] 35 | ``` 36 | address_tx -a 0xa8b919680258d369114910511cc87595aec0be6d 37 | ``` 38 | ### 5. token_holders 39 | show info about token holders [Ethplorer] 40 | required argument 41 | * -a, --address [token on-chain address] 42 | ``` 43 | token_holders -a 0xa8b919680258d369114910511cc87595aec0be6d 44 | ``` 45 | 46 | ### 6. token_price 47 | show info about token price [Ethplorer] 48 | required argument 49 | * -a, --address [token on-chain address] 50 | ``` 51 | token_price -a 0xa8b919680258d369114910511cc87595aec0be6d 52 | ``` 53 | ### 7. token_hist 54 | show historical info about erc20 token [Ethplorer] 55 | required argument 56 | * -a, --address [token on-chain address] 57 | ``` 58 | token_hist -a 0xa8b919680258d369114910511cc87595aec0be6d 59 | ``` 60 | ### 8. token_txs 61 | show info about historical token transactions [Ethplorer] 62 | required argument 63 | * -a, --address [token on-chain address] 64 | ``` 65 | token_txs -a 0xa8b919680258d369114910511cc87595aec0be6d 66 | ``` 67 | 68 | # 69 | ## Luna Terra on-chain data 70 | 71 | ### 1. transaction 72 | show info about transaction [Terra] 73 | required argument 74 | * -a, --address [transaction on-chain address] 75 | ``` 76 | transaction -a F4A64FABEA21BE83B85AB3B599C13844F511CF7035E31D618BEBF614484EBC85 77 | ``` 78 | 79 | ### 2. account_info 80 | show info about terra account [Terra] 81 | required argument 82 | * -a, --address [account on-chain address] 83 | ``` 84 | account_info -a terra1vz0k2glwuhzw3yjau0su5ejk3q9z2zj4ess86s 85 | ``` 86 | 87 | ### 3. staking 88 | show info about staking [Terra] 89 | ``` 90 | staking 91 | ``` 92 | ### 4. supply 93 | show info about terra coins supply [Terra] 94 | ``` 95 | supply 96 | ``` 97 | ### 5. validators 98 | show info about terra validators [Terra] 99 | ``` 100 | validators 101 | ``` -------------------------------------------------------------------------------- /moonbag/onchain/terraluna/terra.py: -------------------------------------------------------------------------------- 1 | from moonbag.onchain.terraluna._client import TerraClient 2 | import pandas as pd 3 | import logging 4 | 5 | 6 | class Terra(TerraClient): 7 | # https://fcd.terra.dev/swagger 8 | 9 | def get_coins_supply(self): 10 | df = pd.DataFrame(self._get_coins_supply()["result"]) 11 | df["amount"] = df["amount"].apply(lambda x: int(x) / 1000000) 12 | return df 13 | 14 | def get_staking_pool(self): 15 | df = pd.Series(self._get_staking_info()["result"]).to_frame().reset_index() 16 | df.columns = ["Metric", "Value"] 17 | return df 18 | 19 | def get_account(self, address): 20 | r = self._get_account(address)["result"] 21 | df = pd.DataFrame() 22 | try: 23 | coins = r["value"]["coins"] 24 | address = r["value"]["address"] 25 | typ = r["type"] 26 | acc_number = r["value"]["account_number"] 27 | for c in coins: 28 | c.update( 29 | {"address": address, "type": typ, "account_number": acc_number} 30 | ) 31 | df = pd.DataFrame(coins) 32 | df["amount"] = df["amount"].apply(lambda x: int(x) / 1000000) 33 | except KeyError as e: 34 | logging.info(e) 35 | return df 36 | 37 | def get_tx(self, address): 38 | r = self._get_tx(address) 39 | try: 40 | logs = r.pop("logs") 41 | transaction_elements = [] 42 | for log in logs: 43 | for event in log["events"]: 44 | for attr in event["attributes"]: 45 | attr.update( 46 | { 47 | "event": event["type"], 48 | "index": log["msg_index"] + 1, 49 | "txhash": r["txhash"], 50 | "timestamp": r["timestamp"], 51 | } 52 | ) 53 | transaction_elements.append(attr) 54 | cols = ["index", "timestamp", "event", "key", "value", "txhash"] 55 | except (KeyError, ValueError) as e: 56 | logging.info(e) 57 | return pd.DataFrame() 58 | return pd.DataFrame(transaction_elements)[cols] 59 | 60 | def get_validators(self): 61 | data = self._get_all_validators()["result"] 62 | for v in data: 63 | commission = v.pop("commission") 64 | v["commission"] = commission["commission_rates"]["rate"] 65 | v["tokens"] = float(v["tokens"]) / 1000000 66 | for col in [ 67 | "unbonding_time", 68 | "unbonding_height", 69 | "consensus_pubkey", 70 | "status", 71 | "delegator_shares", 72 | "jailed", 73 | ]: 74 | v.pop(col) 75 | desc = v.pop("description") 76 | for i in ["security_contact", "details", "identity"]: 77 | desc.pop(i) 78 | v.update(desc) 79 | return pd.DataFrame(data).sort_values(by="tokens", ascending=False) 80 | -------------------------------------------------------------------------------- /moonbag/gecko/utils.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | from datetime import timezone 3 | from dateutil import parser 4 | import json 5 | import pandas as pd 6 | 7 | 8 | def find_discord(item: list) -> list or None: 9 | if isinstance(item, list) and len(item) > 0: 10 | discord = [chat for chat in item if "discord" in chat] 11 | if len(discord) > 0: 12 | return discord[0] 13 | 14 | 15 | def join_list_elements(elem): 16 | if not elem: 17 | raise ValueError("Elem is empty") 18 | if isinstance(elem, dict): 19 | return ", ".join([k for k, v in elem.items()]) 20 | elif isinstance(elem, list): 21 | return ", ".join([k for k in elem]) 22 | else: 23 | return None 24 | 25 | 26 | def filter_list(lst: list) -> list: 27 | if isinstance(lst, list) and len(lst) > 0: 28 | return [i for i in lst if i != ""] 29 | 30 | 31 | def calculate_time_delta(date: str): 32 | now = dt.datetime.now(timezone.utc) 33 | if not isinstance(date, dt.datetime): 34 | date = parser.parse(date) 35 | return (now - date).days 36 | 37 | 38 | def get_eth_addresses_for_cg_coins(file): # pragma: no cover 39 | with open(file, "r") as f: 40 | data = json.load(f) 41 | df = pd.DataFrame(data) 42 | df["ethereum"] = df["platforms"].apply( 43 | lambda x: x.get("ethereum") if "ethereum" in x else None 44 | ) 45 | return df 46 | 47 | 48 | def clean_question_marks(dct: dict): 49 | if isinstance(dct, dict): 50 | for k, v in dct.items(): 51 | if v == "?": 52 | dct[k] = None 53 | 54 | 55 | def replace_qm(df): 56 | df.replace({"?": None, " ?": None}, inplace=True) 57 | return df 58 | 59 | 60 | def get_url(url, elem): # pragma: no cover 61 | return url + elem.find("a")["href"] 62 | 63 | 64 | def clean_row(row): 65 | return [r for r in row.text.strip().split("\n") if r not in ["", " "]] 66 | 67 | 68 | def convert(word): 69 | return "".join(x.capitalize() or "_" for x in word.split("_") if word.isalpha()) 70 | 71 | 72 | def collateral_auditors_parse(args): # pragma: no cover 73 | if args and args[0] == "N/A": 74 | collateral = args[1:] 75 | auditors = [] 76 | else: 77 | n_elem = int(args[0]) 78 | auditors = args[1 : n_elem + 1] 79 | collateral = args[n_elem + 1 :] 80 | 81 | return auditors, collateral 82 | 83 | 84 | def swap_columns(df): 85 | cols = list(df.columns) 86 | cols = [cols[-1]] + cols[:-1] 87 | df = df[cols] 88 | return df 89 | 90 | 91 | def changes_parser(changes): 92 | if isinstance(changes, list) and len(changes) < 3: 93 | for i in range(3 - len(changes)): 94 | changes.append(None) 95 | else: 96 | changes = [None for _ in range(3)] 97 | return changes 98 | 99 | 100 | def remove_keys(entries, the_dict): 101 | for key in entries: 102 | if key in the_dict: 103 | del the_dict[key] 104 | 105 | 106 | def rename_columns_in_dct(dct, mapper): 107 | return {mapper.get(k, v): v for k, v in dct.items()} 108 | 109 | 110 | def create_dictionary_with_prefixes( 111 | columns: [list, tuple], dct: dict, constrains: [list, tuple] = None 112 | ): 113 | results = {} 114 | for column in columns: 115 | ath_data = dct.get(column) 116 | for element in ath_data: 117 | if constrains: 118 | if element in constrains: 119 | results[f"{column}_" + element] = ath_data.get(element) 120 | else: 121 | results[f"{column}_" + element] = ath_data.get(element) 122 | return results 123 | -------------------------------------------------------------------------------- /moonbag/tests/utils/test_gecko_utils.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | from moonbag.gecko.utils import ( 4 | find_discord, 5 | join_list_elements, 6 | filter_list, 7 | calculate_time_delta, 8 | clean_question_marks, 9 | replace_qm, 10 | swap_columns, 11 | changes_parser, 12 | remove_keys, 13 | rename_columns_in_dct, 14 | create_dictionary_with_prefixes, 15 | ) 16 | import datetime as dt 17 | from datetime import timezone 18 | 19 | 20 | def test_should_properly_return_discord_if_exists(): 21 | info = {"chat_url": ["https://discord.com/invite/NK4qdbv", "", ""]} 22 | assert find_discord(info.get("chat_url")) == "https://discord.com/invite/NK4qdbv" 23 | 24 | 25 | def test_should_return_none_if_no_discord_or_no_object(): 26 | info = { 27 | "chat_url": ["", "", ""], 28 | } 29 | info2 = {} 30 | 31 | assert find_discord(info.get("chat_url")) is None 32 | assert find_discord(info2.get("chat_url")) is None 33 | 34 | 35 | def test_join_list_elements(): 36 | assert join_list_elements({"one": 1, "two": 2, "three": 3}) == "one, two, three" 37 | assert join_list_elements(["one, two, three"]) == "one, two, three" 38 | with pytest.raises(ValueError) as e: 39 | join_list_elements([]) 40 | assert "Elem is empty" in str(e.value) 41 | 42 | 43 | def test_filter_list(): 44 | assert filter_list([1, 2, 3, "", ""]) == [1, 2, 3] 45 | assert filter_list([]) is None 46 | assert filter_list(2) is None 47 | 48 | 49 | def test_calculate_time_delta(): 50 | date = dt.datetime.now(timezone.utc) - dt.timedelta(days=2) 51 | assert calculate_time_delta(date) == 2 52 | 53 | 54 | def test_clean_question_marks(): 55 | dct = {"key1": "?", "key2": "data", "key3": "?"} 56 | clean_question_marks(dct) 57 | assert dct == {"key1": None, "key2": "data", "key3": None} 58 | 59 | 60 | def test_replace_qm_in_df(): 61 | df = pd.DataFrame( 62 | columns=["a", "b", "c"], data=[["?", "1", "@"], ["A", "A", "A"], ["?", 1, 2]] 63 | ) 64 | 65 | cleaned_df = pd.DataFrame( 66 | columns=["a", "b", "c"], data=[[None, "1", "@"], ["A", "A", "A"], [None, 1, 2]] 67 | ) 68 | after_clean = replace_qm(df) 69 | assert after_clean.iloc[0, 0] == cleaned_df.iloc[0, 0] 70 | assert after_clean.iloc[2, 0] == cleaned_df.iloc[2, 0] 71 | 72 | 73 | def test_swap_columns(): 74 | df = pd.DataFrame( 75 | columns=["a", "b", "c"], data=[["?", "1", "@"], ["A", "A", "A"], ["?", 1, 2]] 76 | ) 77 | 78 | assert swap_columns(df).columns.tolist() == ["c", "a", "b"] 79 | 80 | 81 | def test_changes_parser(): 82 | assert ( 83 | changes_parser( 84 | [ 85 | "fds", 86 | "fdsf", 87 | ] 88 | ) 89 | == ["fds", "fdsf", None] 90 | ) 91 | assert changes_parser(["fds", "fdsf", 1.2, 154, "asr"]) == [None, None, None] 92 | assert changes_parser([]) == [None, None, None] 93 | 94 | 95 | def test_keys_removal(): 96 | useless_keys = ( 97 | "key2", 98 | "key3", 99 | ) 100 | dct = {"key1": 1, "key2": 2, "key3": 3, "key4": 4} 101 | remove_keys(useless_keys, dct) 102 | assert dct == {"key1": 1, "key4": 4} 103 | 104 | 105 | def test_rename_columns(): 106 | mapper = { 107 | "key1": "a", 108 | "key2": "b", 109 | } 110 | dct = {"key1": 1, "key2": 2, "key3": 3} 111 | assert rename_columns_in_dct(dct, mapper) == {"a": 1, "b": 2, 3: 3} 112 | 113 | 114 | def test_dictionary_with_pref(): 115 | column = ["current_price"] 116 | den = ("usd", "btc", "eth") 117 | dct = { 118 | "current_price": {"usd": 1000, "btc": 200}, 119 | } 120 | 121 | r = create_dictionary_with_prefixes(column, dct, den) 122 | assert r == {"current_price_usd": 1000, "current_price_btc": 200} 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Moon Bag Terminal 3 |

4 | 5 |

6 | 7 |

8 | 9 | ## About The Project 10 | Moonbag is Python Crypto CLI for exploring crypto trends, coins, DeFi protocols, NFTs and others. 11 | It's highly inspired by the famous GamestonkTerminal 12 | 13 | ## Installation 14 | 15 | ## 1. Clone Repository: 16 | ``` 17 | git clone https://github.com/JakubPluta/moonbag.git 18 | ``` 19 | ## 2. Open project folder 20 | ``` 21 | cd moonbag/ 22 | ``` 23 | ## 3. Create and activate virtual environment: 24 | ``` 25 | python -m venv env 26 | source env/Scripts/activate 27 | ``` 28 | ## 4. Install Poetry: 29 | ``` 30 | pip install poetry 31 | ``` 32 | ## 5. Install Dependencies 33 | ``` 34 | poetry install 35 | ``` 36 | 37 | ## 6. Configure API KEYS in order to be able to use all Moon Bag features. 38 | 39 | You need to visit following places to get your API KEYS (all of them are free) 40 | * CryptoCompare: https://min-api.cryptocompare.com/pricing ( "Get your free api key") 41 | * Reddit: https://www.reddit.com/prefs/apps (get client id, client secret and user agent) 42 | * Wales: https://docs.whale-alert.io/#introduction (https://whale-alert.io/signup) 43 | * Cryptopanic: https://cryptopanic.com/developers/api/ 44 | * Ethplorer: https://ethplorer.io/wallet/#api (It's not mandatory, u can use this API without key) 45 | * BitQuery: https://bitquery.io/pricing 46 | 47 | After you get your keys, you need to update it. You can do that in: 48 | * /moonbag/common/keys.py 49 | * or create .env file and store it there. 50 | 51 | ##### Adding keys to moonbag/common/keys.py 52 | * Open key.py file in your editor and add your keys to relevant variables 53 | ``` 54 | WALES_API_KEY = 55 | CC_API_KEY = 56 | REDDIT_CLIENT_ID = 57 | REDDIT_CLIENT_SECRET = 58 | REDDIT_USER_AGENT = 59 | CRYPTO_PANIC_API = 60 | BIT_QUERY_API = 61 | ``` 62 | ##### Adding keys to .env file. 63 | * Create .env file in your main folder 64 | * Open .env file in your editor and add your keys: 65 | ``` 66 | CC_API_KEY= 67 | WALES_API_KEY= 68 | REDDIT_CLIENT_ID= 69 | REDDIT_CLIENT_SECRET= 70 | REDDIT_USER_AGENT= 71 | CRYPTO_PANIC_API= 72 | BIT_QUERY_API = 73 | ``` 74 | 75 | ## 7. Start Moon Bag terminal: 76 | ``` 77 | # In main directory write in terminal: 78 | python moon.py 79 | ``` 80 | If you are running moonbag on windows with bash terminal, and you are facing issues with encoding. Try to write at the beginning 81 | ```bash 82 | chcp.com 65001 83 | set PYTHONIOENCODING=utf-8 84 | ``` 85 | 86 | 87 | ## Disclaimer: 88 | Project is in alpha stage. The test coverage is close to 0. Be aware that there 89 | there will be a lot of issues, bugs. Feel free to report all faced bugs. 90 | Improvements, new functionalities and tests will be added systematically. 91 | 92 | ## Contributing 93 | If you have an idea for improvement, new features. Pull requests are welcome. 94 | 95 | ## License 96 | [MIT](https://choosealicense.com/licenses/mit/) 97 | 98 | ## Available features. (updated: 09.06.2021) 99 | - [x] Coingecko 100 | - [x] Cryptocompare 101 | - [x] Coinpaprika 102 | - [x] UniSwap (GraphQL) 103 | - [x] Funding rates 104 | - [x] Wales txs 105 | - [ ] Airdrops 106 | - [x] DeFi stats 107 | - [x] Defi Pulse 108 | - [x] Lllama 109 | - [x] Fear & Greed Index 110 | - [ ] Blockchain explorers for most popular chains 111 | - [x] Ethereum 112 | - [x] TerraLuna 113 | - [ ] Tests 114 | - [ ] Charts 115 | - [ ] Technical Analysis 116 | - [ ] Social media data 117 | - [x] Reddit 118 | - [x] 4Chan 119 | - [ ] BitcoinTalk 120 | -------------------------------------------------------------------------------- /moon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import os 4 | import argparse 5 | import logging 6 | from argparse import ArgumentError 7 | from moonbag.common import LOGO, MOON 8 | from moonbag.onchain.ethereum import menu as eth_menu 9 | from moonbag.onchain.terraluna import menu as terra_menu 10 | from moonbag.cryptocompare import menu as cc_menu 11 | from moonbag.gecko import coin_menu as coin_menu 12 | from moonbag.gecko import overview_menu as overview_menu 13 | from moonbag.paprika import menu as paprika_menu 14 | from moonbag.discover import menu as disc_menu 15 | 16 | logger = logging.getLogger("main-menu") 17 | 18 | 19 | def help(): 20 | print("Main commands:") 21 | print(" help show help") 22 | print(" r return to previous menu") 23 | print(" quit quit program") 24 | print("") 25 | print("Crypto Overview ") 26 | print(" compare explore crypto-compare data. API key needed [Cryptocompare]") 27 | print(" paprika explore coinpaprika data [Coinpaprika]") 28 | print(" gecko_coin explore CoinGecko data for chosen Coin. [CoinGecko]") 29 | print(" gecko_view explore CoinGecko overview data. [CoinGecko]") 30 | print(" discover explore other Crypto data") 31 | print("") 32 | print("On-chain data ") 33 | print(" ethereum explore on-chain data for ethereum [Ethplorer]") 34 | print(" terra explore on-chain data for terra [TerraAPI]") 35 | print("") 36 | 37 | 38 | mapper = { 39 | "ethereum" : eth_menu.main, 40 | "terra" : terra_menu.main, 41 | "compare" : cc_menu.main, 42 | "gecko_coin" : coin_menu.main, 43 | "gecko_view" : overview_menu.main, 44 | "paprika" : paprika_menu.main, 45 | "discover" : disc_menu.main, 46 | 47 | 48 | } 49 | 50 | 51 | def main(): 52 | choices = ["help", "exit", "quit", "r", "q", "terra", "ethereum", "paprika","gecko_coin", "gecko_view", "compare", "discover"] 53 | if sys.platform == "win32": 54 | os.system("") 55 | 56 | parser = argparse.ArgumentParser(prog="moonbag", add_help=False) 57 | parser.add_argument("cmd", choices=choices) 58 | 59 | show_help = True 60 | print(LOGO) 61 | 62 | while True: 63 | 64 | try: 65 | sys.stdin.reconfigure(encoding="utf-8") 66 | sys.stdout.reconfigure(encoding="utf-8") 67 | sys.stdout.encoding = 'cp65001' 68 | 69 | except Exception as e: 70 | print(e, "\n") 71 | 72 | home = False 73 | 74 | if show_help: 75 | help() 76 | show_help = False 77 | 78 | an_input = input(f"{MOON}> ") 79 | if not an_input: 80 | print("") 81 | continue 82 | 83 | try: 84 | parsy, others = parser.parse_known_args(an_input.split()) 85 | cmd = parsy.cmd 86 | 87 | except ArgumentError: 88 | print("The command selected doesn't exist") 89 | print("") 90 | continue 91 | 92 | except SystemExit: 93 | print("The command selected doesn't exist") 94 | print("") 95 | continue 96 | 97 | menu = False 98 | 99 | if cmd == "help": 100 | show_help = True 101 | 102 | elif cmd in ["exit", "quit", "q", 'r']: 103 | break 104 | 105 | view = mapper.get(cmd) 106 | if view is None: 107 | continue 108 | elif callable(view): 109 | print(f"Going to {cmd}") 110 | menu = view() 111 | else: 112 | print("Something went wrong") 113 | 114 | if menu: 115 | break 116 | 117 | if not home: 118 | show_help = True 119 | 120 | print("Let the moonlight always fill your bags. See you again!") 121 | 122 | 123 | if __name__ == "__main__": 124 | main() 125 | -------------------------------------------------------------------------------- /moonbag/paprika/_client.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import requests 4 | from retry import retry 5 | from requests.adapters import HTTPAdapter 6 | 7 | 8 | ENDPOINTS = { 9 | "global": "/global", 10 | "coins": "/coins", 11 | "coin_tweeter": "/coins/{}/twitter", 12 | "coin_events": "/coins/{}/events", 13 | "coin_exchanges": "/coins/{}/exchanges", 14 | "coin_markets": "/coins/{}/markets", 15 | "ohlcv": "/coins/{}/ohlcv/latest", 16 | "ohlcv_hist": "/coins/{}/ohlcv/historical", 17 | "people": "/people/{}", 18 | "tickers": "/tickers", 19 | "ticker_info": "/tickers/{}", 20 | "exchanges": "/exchanges", 21 | "exchange_info": "/exchanges/{}", 22 | "exchange_markets": "/exchanges/{}/markets", 23 | "contract_platforms": "/contracts", 24 | "contract_platform_addresses": "/contracts/{}", 25 | "search": "/search", 26 | } 27 | 28 | 29 | class Client: 30 | BASE_URL = "https://api.coinpaprika.com/v1" 31 | 32 | def __init__(self): 33 | self.header = {"Accept": "application/json", "User-Agent": "moonbag"} 34 | self.s = requests.Session() 35 | self.s.mount(self.BASE_URL, HTTPAdapter(max_retries=5)) 36 | 37 | @retry(tries=2, max_delay=5) 38 | def _make_request(self, endpoint, payload=None, **kwargs): 39 | url = self.BASE_URL + endpoint 40 | if payload is None: 41 | payload = {} 42 | if kwargs: 43 | payload.update(kwargs) 44 | return requests.get(url, params=payload).json() 45 | 46 | def _get_global_market(self): 47 | return self._make_request(ENDPOINTS["global"]) 48 | 49 | def _get_coins(self): 50 | return self._make_request(ENDPOINTS["coins"]) 51 | 52 | def _get_coin_twitter_timeline(self, coin_id="eth-ethereum"): 53 | return self._make_request(ENDPOINTS["coin_tweeter"].format(coin_id)) 54 | 55 | def _get_coin_events_by_id(self, coin_id="eth-ethereum"): 56 | return self._make_request(ENDPOINTS["coin_events"].format(coin_id)) 57 | 58 | def _get_coin_exchanges_by_id(self, coin_id="eth-ethereum"): 59 | return self._make_request(ENDPOINTS["coin_exchanges"].format(coin_id)) 60 | 61 | def _get_coin_markets_by_id(self, coin_id="eth-ethereum", quotes="USD,BTC"): 62 | return self._make_request( 63 | ENDPOINTS["coin_markets"].format(coin_id), quotes=quotes 64 | ) 65 | 66 | def _get_ohlc_last_day(self, coin_id="eth-ethereum", quotes="USD"): 67 | """ "string Default: "usd" 68 | returned data quote (available values: usd btc)""" 69 | return self._make_request(ENDPOINTS["ohlcv"].format(coin_id), quotes=quotes) 70 | 71 | def _get_ohlc_historical( 72 | self, coin_id="eth-ethereum", quotes="USD", start=None, end=None 73 | ): 74 | if end is None: 75 | end = datetime.datetime.now().strftime("%Y-%m-%d") 76 | 77 | if start is None: 78 | start = (datetime.datetime.now() - datetime.timedelta(days=365)).strftime( 79 | "%Y-%m-%d" 80 | ) 81 | """"string Default: "usd" 82 | returned data quote (available values: usd btc)""" 83 | return self._make_request( 84 | ENDPOINTS["ohlcv_hist"].format(coin_id), quotes=quotes, start=start, end=end 85 | ) 86 | 87 | def _get_people(self, person_id="vitalik-buterin"): 88 | return self._make_request(ENDPOINTS["people"].format(person_id)) 89 | 90 | def _get_tickers_for_all_coins(self, quotes="USD,BTC"): 91 | return self._make_request(ENDPOINTS["tickers"], quotes=quotes) 92 | 93 | def _get_tickers_for_coin(self, coin_id="btc-bitcoin", quotes="USD,BTC"): 94 | return self._make_request( 95 | ENDPOINTS["ticker_info"].format(coin_id), quotes=quotes 96 | ) 97 | 98 | def _get_exchanges(self, quotes="USD,BTC"): 99 | return self._make_request(ENDPOINTS["exchanges"], quotes=quotes) 100 | 101 | def _get_exchange_by_id(self, exchange_id="binance", quotes="USD,BTC"): 102 | return self._make_request( 103 | ENDPOINTS["exchange_info"].format(exchange_id), quotes=quotes 104 | ) 105 | 106 | def _get_exchange_markets(self, exchange_id="binance", quotes="USD,BTC"): 107 | return self._make_request( 108 | ENDPOINTS["exchange_markets"].format(exchange_id), quotes=quotes 109 | ) 110 | 111 | def _search(self, q, c=None, modifier=None): 112 | """ 113 | q: phrase for search 114 | 115 | c: one or more categories (comma separated) to search. 116 | Available options: currencies|exchanges|icos|people|tags 117 | Default: currencies,exchanges,icos,people,tags 118 | 119 | modifier: set modifier for search results. 120 | Available options: symbol_search - search only by symbol (works for currencies only) 121 | """ 122 | if c is None: 123 | c = "currencies,exchanges,icos,people,tags" 124 | return self._make_request( 125 | ENDPOINTS["search"], q=q, c=c, modifier=modifier, limit=100 126 | ) 127 | 128 | def _get_all_contract_platforms( 129 | self, 130 | ): 131 | return self._make_request(ENDPOINTS["contract_platforms"]) 132 | 133 | def _get_contract_platforms(self, platform_id="eth_ethereum"): 134 | return self._make_request( 135 | ENDPOINTS["contract_platform_addresses"].format(platform_id) 136 | ) 137 | -------------------------------------------------------------------------------- /moonbag/onchain/terraluna/menu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import os 4 | import argparse 5 | import logging 6 | from moonbag.common import LOGO, MOON, print_table 7 | from argparse import ArgumentError 8 | from inspect import signature 9 | from moonbag.common.utils import MoonParser 10 | from moonbag.onchain.terraluna.terra import Terra 11 | 12 | logger = logging.getLogger("terra-menu") 13 | 14 | 15 | class Controller: 16 | def __init__(self): 17 | self.client = Terra() 18 | self.parser = argparse.ArgumentParser(prog="terra", add_help=False) 19 | self.parser.add_argument("cmd") 20 | self.base_commands = ["help", "exit", "quit", "r", "q"] 21 | self.mapper = { 22 | "supply": self.show_supply, 23 | "staking": self.show_staking, 24 | "account_info": self.show_address_info, 25 | "transaction": self.show_tx_info, 26 | "validators": self.show_validators, 27 | } 28 | 29 | @staticmethod 30 | def help(): 31 | print("Main commands:") 32 | print(" help show help") 33 | print(" r return to previous menu") 34 | print(" quit quit program") 35 | print("") 36 | print("Terra ") 37 | print(" transaction show info about transaction [Terra]") 38 | print(" account_info show info about account_info [Terra]") 39 | print(" staking show info about staking [Terra]") 40 | print(" supply show info about terra coins supply [Terra]") 41 | print(" validators show info about terra validators [Terra]") 42 | 43 | print(" ") 44 | return 45 | 46 | def show_tx_info(self, args): 47 | parser = MoonParser( 48 | prog="transaction info", 49 | add_help=True, 50 | description="transaction details", 51 | ) 52 | parser.add_address_argument(help="transaction hash") 53 | parsy, _ = parser.parse_known_args(args) 54 | try: 55 | data = self.client.get_tx(parsy.address) 56 | except ValueError as e: 57 | print(f"{e}") 58 | return 59 | print_table(data) 60 | 61 | def show_address_info(self, args): 62 | parser = MoonParser( 63 | prog="terra address info", 64 | add_help=True, 65 | description="get address details", 66 | ) 67 | parser.add_address_argument(help="account address") 68 | parsy, _ = parser.parse_known_args(args) 69 | try: 70 | data = self.client.get_account(parsy.address) 71 | except ValueError as e: 72 | print(f"{e}") 73 | return 74 | print_table(data) 75 | 76 | def show_staking(self, args): 77 | parser = MoonParser( 78 | prog="staking info", 79 | add_help=True, 80 | description="show staking details", 81 | ) 82 | parsy, _ = parser.parse_known_args(args) 83 | try: 84 | data = self.client.get_staking_pool() 85 | except ValueError as e: 86 | print(f"{e}") 87 | return 88 | print_table(data) 89 | 90 | def show_supply(self, args): 91 | parser = MoonParser( 92 | prog="supply info", 93 | add_help=True, 94 | description="show terra coins supply details", 95 | ) 96 | parsy, _ = parser.parse_known_args(args) 97 | try: 98 | data = self.client.get_coins_supply() 99 | except ValueError as e: 100 | print(f"{e}") 101 | return 102 | print_table(data) 103 | 104 | def show_validators(self, args): 105 | parser = MoonParser( 106 | prog="validators info", 107 | add_help=True, 108 | description="show terra validators", 109 | ) 110 | parsy, _ = parser.parse_known_args(args) 111 | try: 112 | data = self.client.get_validators() 113 | except ValueError as e: 114 | print(f"{e}") 115 | return 116 | print_table(data) 117 | 118 | 119 | def main(): 120 | c = Controller() 121 | choices = c.base_commands + list(c.mapper.keys()) 122 | if sys.platform == "win32": 123 | os.system("") 124 | 125 | parser = argparse.ArgumentParser(prog="terra", add_help=False) 126 | parser.add_argument("cmd", choices=choices) 127 | 128 | print(LOGO) 129 | c.help() 130 | while True: 131 | 132 | an_input = input(f"{MOON}> (terra) ") 133 | try: 134 | parsy, others = parser.parse_known_args(an_input.split()) 135 | cmd = parsy.cmd 136 | 137 | if cmd == "help": 138 | c.help() 139 | elif cmd in ["exit", "quit", "q"]: 140 | return True 141 | elif cmd == "r": 142 | return False 143 | 144 | view = c.mapper.get(cmd) 145 | if view is None: 146 | continue 147 | elif callable( 148 | view 149 | ): # If function takes params return func(args), else func() 150 | if len(signature(view).parameters) > 0: 151 | view(others) 152 | else: 153 | view() 154 | 155 | except ArgumentError: 156 | print("The command selected doesn't exist") 157 | print(" ") 158 | continue 159 | 160 | except SystemExit: 161 | print(" ") 162 | print(" ") 163 | continue 164 | -------------------------------------------------------------------------------- /moonbag/common/utils.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | import pandas as pd 3 | import datetime 4 | import logging 5 | import argparse 6 | import os 7 | 8 | logger = logging.getLogger("utils") 9 | 10 | 11 | def formatter(x): 12 | if isinstance(x, int): 13 | return "{0:.2f}".format(x) 14 | elif isinstance(x, float): 15 | if x > 10: 16 | return "{0:.2f}".format(x) 17 | else: 18 | return "{0:.6f}".format(x) 19 | return x 20 | 21 | 22 | def table_formatter(func): # pragma: no cover 23 | def wrapper(*args, **kwargs): 24 | df = func(*args, **kwargs) 25 | df = df.applymap(lambda x: formatter(x)) 26 | return df 27 | 28 | return wrapper 29 | 30 | 31 | # FIXME Make this func more general 32 | def wrap_text_in_df(df: pd.DataFrame, w=55): # pragma: no cover 33 | return df.applymap( 34 | lambda x: "\n".join(textwrap.wrap(x, width=w)) if isinstance(x, str) else x 35 | ) 36 | 37 | 38 | def underscores_to_newline_replace(cols: list, line: int = 13): 39 | return [ 40 | textwrap.fill(c.replace("_", " "), line, break_long_words=False) 41 | for c in list(cols) 42 | ] 43 | 44 | 45 | def wrap_headers_in_dataframe(df: pd.DataFrame, n=15, replace=None): 46 | if replace: 47 | return [ 48 | textwrap.fill(c.replace(replace, " "), n, break_long_words=False) 49 | for c in list(df.columns) 50 | ] 51 | return [textwrap.fill(c, n, break_long_words=False) for c in list(df.columns)] 52 | 53 | 54 | def created_date(timestamp): 55 | return datetime.datetime.fromtimestamp(timestamp) 56 | 57 | 58 | BASE_PARSER_ARGUMENTS = { 59 | "coin": dict( 60 | help="Coin symbol", 61 | dest="symbol", 62 | required=True, 63 | default="ETH", 64 | ), 65 | "limit": dict( 66 | help="Number of results", 67 | dest="limit", 68 | required=False, 69 | type=int, 70 | default=50, 71 | ), 72 | "exchange": dict( 73 | help="Name of exchange", 74 | dest="exchange", 75 | required=False, 76 | type=str, 77 | default="Binance", 78 | ), 79 | "tosymbol": dict( 80 | help="To symbol - coin in which you want to see data", 81 | dest="tosymbol", 82 | required=False, 83 | type=str, 84 | default="USD", 85 | ), 86 | "key": dict( 87 | help="What you need recommendations for ? choose from [wallet, exchange]", 88 | dest="key", 89 | required=False, 90 | type=str, 91 | choices=["wallet", "exchange"], 92 | default="wallet", 93 | ), 94 | "sort": dict( 95 | help="Sorting [latest, popular]", 96 | dest="sort", 97 | required=False, 98 | type=str, 99 | default="latest", 100 | choices=["latest", "popular"], 101 | ), 102 | "address": dict( 103 | help="Token on-chain address", 104 | dest="address", 105 | required=True, 106 | type=str, 107 | ), 108 | } 109 | 110 | 111 | class MoonParser(argparse.ArgumentParser): 112 | list_of_arguments = ["help", "dest", "required", "type", "choices", "default"] 113 | 114 | def _modify_default_dict_of_arguments(self, dct: dict, **kwargs): 115 | if kwargs: 116 | for argument in self.list_of_arguments: 117 | if argument in kwargs: 118 | dct[argument] = kwargs.get(argument) 119 | return dct 120 | 121 | def add_coin_argument(self, **kwargs): # pragma: no cover 122 | dct = self._modify_default_dict_of_arguments( 123 | BASE_PARSER_ARGUMENTS["coin"], **kwargs 124 | ) 125 | self.add_argument("-c", "--coin", "--fsym", **dct) 126 | 127 | def add_to_symbol_argument(self, **kwargs): # pragma: no cover 128 | dct = self._modify_default_dict_of_arguments( 129 | BASE_PARSER_ARGUMENTS["tosymbol"], **kwargs 130 | ) 131 | self.add_argument("-t", "--tsym", "--to", **dct) 132 | 133 | def add_limit_argument(self, **kwargs): # pragma: no cover 134 | dct = self._modify_default_dict_of_arguments( 135 | BASE_PARSER_ARGUMENTS["limit"], **kwargs 136 | ) 137 | self.add_argument("-n", "--limit", **dct) 138 | 139 | def add_exchange_argument(self, **kwargs): # pragma: no cover 140 | dct = self._modify_default_dict_of_arguments( 141 | BASE_PARSER_ARGUMENTS["exchange"], **kwargs 142 | ) 143 | self.add_argument("-e", "--exchange", **dct) 144 | 145 | def add_key_argument(self, **kwargs): # pragma: no cover 146 | dct = self._modify_default_dict_of_arguments( 147 | BASE_PARSER_ARGUMENTS["key"], **kwargs 148 | ) 149 | self.add_argument("-k", "--key", **dct) 150 | 151 | def add_sort_argument(self, **kwargs): # pragma: no cover 152 | dct = self._modify_default_dict_of_arguments( 153 | BASE_PARSER_ARGUMENTS["sort"], **kwargs 154 | ) 155 | self.add_argument("-s", "--sort", **dct) 156 | 157 | def add_address_argument(self, **kwargs): # pragma: no cover 158 | dct = self._modify_default_dict_of_arguments( 159 | BASE_PARSER_ARGUMENTS["address"], **kwargs 160 | ) 161 | self.add_argument("-a", "--address", **dct) 162 | 163 | 164 | def clear(): 165 | from sys import platform as _platform 166 | 167 | if _platform == "linux" or _platform == "linux2": 168 | os.system("clear") 169 | elif _platform == "darwin": 170 | os.system("clear") 171 | elif _platform == "win32" or _platform == "win64": 172 | os.system("cls") 173 | -------------------------------------------------------------------------------- /moonbag/gecko/README.md: -------------------------------------------------------------------------------- 1 | ## How to use Gecko 2 | There are to options to use CoinGecko views: 3 | - gecko_coin - view in which you can see data for particular coin 4 | - gecko_view - view in which you can see different data for defi, nft, top gainer, news etc. 5 | 6 | 7 | ## gecko_coin - see data for given coin 8 | 9 | ### 1. similar 10 | primitive way to find coin names, and symbols with by search query 11 | required argument 12 | * -c, --coin [coin symbol] 13 | ``` 14 | similar -c uniswap 15 | ``` 16 | ### 2. load 17 | load coin from CoinGecko. 18 | required argument 19 | * -c, --coin [coin symbol] 20 | ``` 21 | load -c uniswap 22 | ``` 23 | ### 3. coinlist 24 | show all available coins 25 | ``` 26 | coinlist 27 | ``` 28 | ### 4. info 29 | show info for loaded coin 30 | ``` 31 | info 32 | ``` 33 | ### 5. market 34 | show market date for loaded coin 35 | ``` 36 | market 37 | ``` 38 | ### 6. devs 39 | show data about coin development (Github, Bitbucket). For most coins data is unavailable 40 | ``` 41 | devs 42 | ``` 43 | ### 7. ath 44 | show info all time high of loaded coin 45 | ``` 46 | ath 47 | ``` 48 | ### 8. atl 49 | show info all time low of loaded coin 50 | ``` 51 | atl 52 | ``` 53 | ### 9. web 54 | show web pages founded for loaded coin 55 | ``` 56 | web 57 | ``` 58 | ### 10. explorers 59 | show blockchain explorers links for loaded coin 60 | ``` 61 | explorers 62 | ``` 63 | 64 | 65 | 66 | ## gecko_view - see overall crypto data 67 | 68 | ### 1. news 69 | show data for latest crypto news 70 | optional arguments 71 | * -n, --num [number of records that you want to see] 72 | ``` 73 | news -n 30 # displays 30 news 74 | ``` 75 | ### 2. trending 76 | show data for most trending coins on CoinGecko 77 | optional arguments 78 | * -n, --num [number of records that you want to see] 79 | ``` 80 | trending -n 30 # display 30 trending coins 81 | ``` 82 | ### 3. recently 83 | show data for recently added coins on CoinGecko 84 | optional arguments 85 | * -n, --num [number of records that you want to see] 86 | ``` 87 | recently -n 30 # display 30 trending coins 88 | ``` 89 | ### 4. most_visited 90 | show data for most visited coins on CoinGecko 91 | optional arguments 92 | * -n, --num [number of records that you want to see] 93 | ``` 94 | most_visited -n 30 95 | ``` 96 | ### 5. most_voted 97 | show data for most voted coins on CoinGecko 98 | optional arguments 99 | * -n, --num [number of records that you want to see] 100 | ``` 101 | most_voted -n 30 102 | ``` 103 | ### 5. gainers 104 | show data for top gainers on CoinGecko 105 | optional arguments 106 | * -n, --num [number of records that you want to see] 107 | ``` 108 | gainers -n 30 109 | ``` 110 | ### 6. losers 111 | show data for biggest losers on CoinGecko 112 | optional arguments 113 | * -n, --num [number of records that you want to see] 114 | ``` 115 | losers -n 30 116 | ``` 117 | ### 7. top_sentiment 118 | show data for coins with most positive sentiment on CoinGecko 119 | optional arguments 120 | * -n, --num [number of records that you want to see] 121 | ``` 122 | top_sentiment -n 30 123 | ``` 124 | ### 8. top_volume 125 | show data for coins with highest volume on CoinGecko 126 | optional arguments 127 | * -n, --num [number of records that you want to see] 128 | ``` 129 | top_volume -n 30 130 | ``` 131 | ### 9. top_dexes 132 | show data for top decentralized exchanges on CoinGecko 133 | optional arguments 134 | * -n, --num [number of records that you want to see] 135 | ``` 136 | top_dexes -n 30 137 | ``` 138 | ### 10. top_defi 139 | show data for top defi coins on CoinGecko 140 | optional arguments 141 | * -n, --num [number of records that you want to see] 142 | ``` 143 | top_defi -n 30 144 | ``` 145 | ### 11. info defi 146 | show overall data about defi market on CoinGecko 147 | ``` 148 | info_defi 149 | ``` 150 | ### 12. yield_farms 151 | show data for top yield farms on CoinGecko 152 | optional arguments 153 | * -n, --num [number of records that you want to see] 154 | ``` 155 | yield_farms 156 | ``` 157 | ### 13. stables 158 | show data for stablecoins on CoinGecko 159 | optional arguments 160 | * -n, --num [number of records that you want to see] 161 | ``` 162 | stables 163 | ``` 164 | ### 14. top_nft 165 | show data for top non-fungible tokens on CoinGecko 166 | optional arguments 167 | * -n, --num [number of records that you want to see] 168 | ``` 169 | top_nft 170 | ``` 171 | ### 15. nft_market 172 | show overall data bout non-fungible tokens market on CoinGecko 173 | ``` 174 | nft_market 175 | ``` 176 | ### 16. nft_of_day 177 | show nft of the day on CoinGecko 178 | ``` 179 | nft_of_day 180 | ``` 181 | ### 17. categories 182 | show top crypto categories CoinGecko 183 | optional arguments 184 | * -n, --num [number of records that you want to see] 185 | ``` 186 | categories -n 30 187 | ``` 188 | ### 18. derivatives 189 | show derivatives CoinGecko [long waiting time 15 sec] 190 | optional arguments 191 | * -n, --num [number of records that you want to see] 192 | ``` 193 | derivatives -n 30 194 | ``` 195 | ### 19. indexes 196 | show indexes CoinGecko 197 | optional arguments 198 | * -n, --num [number of records that you want to see] 199 | ``` 200 | indexes -n 30 201 | ``` 202 | ### 20. fin_products 203 | show crypto financial products CoinGecko 204 | optional arguments 205 | * -n, --num [number of records that you want to see] 206 | ``` 207 | fin_products -n 30 208 | ``` 209 | ### 21. fin_platforms 210 | show crypto financial platforms CoinGecko 211 | optional arguments 212 | * -n, --num [number of records that you want to see] 213 | ``` 214 | fin_platforms -n 30 215 | ``` 216 | ### 22. btc_comp 217 | show companies that holds bitcoin CoinGecko 218 | ``` 219 | btc_comp 220 | ``` 221 | ### 23. eth_comp 222 | show companies that holds ethereum CoinGecko 223 | ``` 224 | eth_comp 225 | ``` 226 | ### 24. eth_holdings 227 | show overall info about companies that holds ethereum CoinGecko 228 | ``` 229 | eth_holdings 230 | ``` 231 | ### 25. btc_holdings 232 | show overall info about companies that holds btc CoinGecko 233 | ``` 234 | btc_holdings 235 | ``` 236 | ### 26. exchanges 237 | show info about exchanges CoinGecko 238 | ``` 239 | exchanges 240 | ``` 241 | ### 27. ex_rates 242 | show info about exchange rates CoinGecko 243 | ``` 244 | ex_rates 245 | ``` -------------------------------------------------------------------------------- /moonbag/cryptocompare/README.md: -------------------------------------------------------------------------------- 1 | ## How to use CryptoCompare 2 | To use crypto compare view. You need to have visit https://min-api.cryptocompare.com/, register and generate free api key. 3 | After that go to moonbag/common/keys.py and paste it under CC_API_KEY = 4 | or do that in .env file if you this approach 5 | 6 | ### 1. news 7 | show latest crypto news [CryptoCompare] 8 | arguments: 9 | -s, --sort {latest, popular} 10 | ``` 11 | news -s latest 12 | ``` 13 | ### 2. similar 14 | try to find coin symbol [CryptoCompare] 15 | arguments: 16 | * -c, --coin SYMBOL 17 | * -k, --key {symbol,name} [you can search by coin symbol, or by coin name] 18 | ``` 19 | similar -c uniswap -k name 20 | ``` 21 | ### 3. coins 22 | show available coins: symbol and names [CryptoCompare] 23 | ``` 24 | coins 25 | ``` 26 | ### 4. top_mcap 27 | show top market cap coins [CryptoCompare] 28 | arguments: 29 | * -t, --tsym ,--to [coin in which you want to see market cap data. Default USD] 30 | * -n, --limit [number of results] 31 | ``` 32 | top_mcap -t USD -n 100 33 | ``` 34 | ### 5. top_exchanges 35 | arguments: 36 | show top exchanges for given pair: Default BTC/USD [CryptoCompare] 37 | * -c , --coin [coin symbol # Default BTC] 38 | * -t, --tsym ,--to [coin in which you want to see market cap data. Default USD] 39 | ``` 40 | top_exchanges -c BTC -t USD 41 | ``` 42 | ### 6. list_exchanges 43 | show names of all exchanges [CryptoCompare] 44 | ``` 45 | list_exchanges 46 | ``` 47 | ### 7. top_symbols_ex 48 | show top coins on given exchange [CryptoCompare] 49 | arguments: 50 | * -e, --exchange [name of exchange] 51 | * -n, --limit [number of results] 52 | ``` 53 | top_symbols_ex -e binance -n 30 54 | ``` 55 | ### 8. orders 56 | show order book for given pair and exchange. LUNA/BTC,Binance [CryptoCompare] 57 | arguments: 58 | * -c , --coin [coin symbol] 59 | * -t, --tsym ,--to [coin in which you want to see market cap data. Default USD] 60 | * -e, --exchange [name of exchange] 61 | 62 | ``` 63 | orders -c LUNA -t BTC -e binance 64 | ``` 65 | ### 9. orders_snap 66 | show order book snapshot for given pair and exchange. LUNA/BTC,Binance [CryptoCompare] 67 | arguments: 68 | * -c , --coin [coin symbol] 69 | * -t, --tsym ,--to [coin in which you want to see market cap data. Default USD ] 70 | * -e, --exchange [name of exchange] 71 | ``` 72 | orders_snap -c LUNA -t BTC -e binance 73 | ``` 74 | 75 | ### 10. price 76 | show latest price info for given pair like BTC/USD [CryptoCompare] 77 | arguments: 78 | * -c , --coin [coin symbol] 79 | * -t, --tsym ,--to [coin in which you want to see market cap data. Default USD] 80 | ``` 81 | price -c ETH -t BTC 82 | ``` 83 | ### 10. price_day 84 | show historical prices with 1 day interval [CryptoCompare] 85 | arguments: 86 | * -c , --coin [coin symbol] 87 | * -t, --tsym ,--to [coin in which you want to see market cap data. Default USD] 88 | * -n, --limit [limit of records] 89 | ``` 90 | price_day -c ETH -t BTC -n 100 91 | ``` 92 | ### 11. price_hour 93 | show historical prices with 1 hour interval [CryptoCompare] 94 | arguments: 95 | * -c , --coin [coin symbol] 96 | * -t, --tsym ,--to [coin in which you want to see market cap data. Default USD] 97 | * -n, --limit [limit of records] 98 | ``` 99 | price_hour -c ETH -t BTC -n 100 100 | ``` 101 | ### 12. price_minute 102 | show latest price info for given pair like BTC/USD [CryptoCompare] 103 | arguments: 104 | * -c , --coin [coin symbol] 105 | * -t, --tsym ,--to [coin in which you want to see market cap data. Default USD] 106 | * -n, --limit [limit of records] 107 | ``` 108 | price_minute -c ETH -t BTC -n 100 109 | ``` 110 | ### 13. volume_day 111 | show daily volume for given pair. Default: BTC/USD [CryptoCompare] 112 | arguments: 113 | * -c , --coin [coin symbol] 114 | * -t, --tsym ,--to [coin in which you want to see market cap data. Default USD] 115 | * -n, --limit [limit of records] 116 | ``` 117 | volume_day -c ETH -t BTC -n 100 118 | ``` 119 | ### 14. volume_hour 120 | show hourly volume for given pair. Default: BTC/USD [CryptoCompare] 121 | arguments: 122 | * -c , --coin [coin symbol] 123 | * -t, --tsym ,--to [coin in which you want to see market cap data. Default USD] 124 | * -n, --limit [limit of records] 125 | ``` 126 | volume_hour -c ETH -t BTC -n 100 127 | ``` 128 | ### 15. trade_signals 129 | show latest trading signals for given coin. Default ETH [CryptoCompare] (works only for few coins) 130 | arguments: 131 | * -c , --coin [coin symbol] 132 | ``` 133 | trade_signals -c ETH 134 | ``` 135 | ### 16. pair_volume 136 | show latest volume for given pair of coins [CryptoCompare] 137 | arguments: 138 | * -t , --to, --tsym [coin in which you want to see market cap data. Default USD] 139 | ``` 140 | pair_volume 141 | ``` 142 | ### 17. top_trading 143 | show top trading pairs for given coin [CryptoCompare]. Default BTC 144 | arguments: 145 | * -c , --coin [coin symbol] 146 | ``` 147 | top_trading -c BTC 148 | ``` 149 | ### 18. list_bc_coins 150 | show list of coins with on-chain data available [CryptoCompare] 151 | ``` 152 | list_bc_coins 153 | ``` 154 | ### 19. coin_bc 155 | show on chain data for given coin [CryptoCompare] 156 | arguments: 157 | * -c , --coin [coin symbol] 158 | ``` 159 | coin_bc -c BTC 160 | ``` 161 | ### 20. coin_bc_hist 162 | show historical on chain data for given coin [CryptoCompare] 163 | arguments: 164 | * -c , --coin [coin symbol] 165 | ``` 166 | coin_bc_hist -c BTC 167 | ``` 168 | ### 21. wallets 169 | show all available wallets [CryptoCompare] 170 | ``` 171 | wallets 172 | ``` 173 | ### 22. gambling 174 | show all available gambling [CryptoCompare] 175 | ``` 176 | gambling 177 | ``` 178 | ### 23. recommended 179 | show all recommendation for wallets and exchanges [CryptoCompare] 180 | arguments: 181 | * -c , --coin [coin symbol] 182 | * -k, --key {exchange,wallet} [recommended exchanges or wallets for given coin] 183 | ``` 184 | recommended -c ETH -k wallet 185 | ``` 186 | ### 24. social 187 | show latest social stats for given coins symbol (works only for few coins) 188 | arguments: 189 | * -c , --coin [coin symbol] 190 | ``` 191 | social -c ETH 192 | ``` 193 | ### 25. social 194 | show historical social stats for given coins symbol, weekly aggregated. Works only for few coins) 195 | arguments: 196 | * -c , --coin [coin symbol] 197 | ``` 198 | social_hist -c ETH 199 | ``` 200 | -------------------------------------------------------------------------------- /moonbag/gecko/overview_menu.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import pandas as pd 3 | from moonbag.gecko.gecko import Overview 4 | import logging 5 | from moonbag.common import LOGO, MOON, print_table 6 | from typing import List 7 | 8 | logger = logging.getLogger("parser") 9 | 10 | 11 | def create_view(name, func, desc, n: List): 12 | """Method that wraps function with argparser, with top n argument as default""" 13 | parser = argparse.ArgumentParser(add_help=True, prog=name, description=desc) 14 | parser.add_argument( 15 | "-n", 16 | "--num", 17 | action="store", 18 | dest="n", 19 | type=int, 20 | default=50, 21 | help="Number of the records", 22 | ) 23 | parsy, _ = parser.parse_known_args(n) 24 | if not parsy: 25 | return 26 | 27 | df = func(n=parsy.n) 28 | return df 29 | 30 | 31 | class Controller: 32 | def __init__(self): 33 | self.parser = argparse.ArgumentParser(prog="overview", add_help=False) 34 | self.parser.add_argument("cmd") 35 | self.o = Overview() 36 | self.base = { 37 | "help": self.help, 38 | "r": self.returner, 39 | "quit": self.quit, 40 | "exit": self.quit, 41 | } 42 | self.mapper = { 43 | "top_dexes": self.o.get_top_dexes, 44 | "most_visited": self.o.get_most_visited_coins, 45 | "gainers": self.o.get_top_gainers, 46 | "most_voted": self.o.get_most_voted_coins, 47 | "top_sentiment": self.o.get_positive_sentiment_coins, 48 | "top_volume": self.o.get_top_volume_coins, 49 | "trending": self.o.get_trending_coins, 50 | "yield_farms": self.o.get_yield_farms, 51 | "stables": self.o.get_stable_coins, 52 | "top_nft": self.o.get_top_nfts, 53 | "nft_market": self.o.get_nft_market_status, 54 | "categories": self.o.get_top_crypto_categories, 55 | "nft_of_day": self.o.get_nft_of_the_day, 56 | "recently": self.o.get_recently_added_coins, 57 | "btc_comp": self.o.get_companies_with_btc, 58 | "eth_comp": self.o.get_companies_with_eth, 59 | "losers": self.o.get_top_losers, 60 | "ex_rates": self.o.get_exchange_rates, 61 | "exchanges": self.o.get_exchanges, 62 | "derivatives": self.o.get_derivatives, 63 | "indexes": self.o.get_indexes, 64 | "eth_holdings": self.o.get_eth_holdings_public_companies_overview, 65 | "btc_holdings": self.o.get_btc_holdings_public_companies_overview, 66 | "news": self.o.get_news, 67 | "top_defi": self.o.get_top_defi_coins, 68 | "info_defi": self.o.get_global_defi_info, 69 | "fin_products": self.o.get_finance_products, 70 | "fin_platforms": self.o.get_financial_platforms, 71 | } 72 | 73 | @staticmethod 74 | def help(): 75 | print("Main commands:") 76 | print(" help show help") 77 | print(" r return to previous menu") 78 | print(" quit quit program") 79 | print("") 80 | print("Crypto Overview (use --n 25 for top N records)") 81 | print(" news show latest crypto news [Coingecko]") 82 | print(" trending show trending coins [Coingecko]") 83 | print(" recently show recently added coins [Coingecko]") 84 | print(" most_visited show most visited coins [Coingecko]") 85 | print(" most_voted show most visited coins [Coingecko]") 86 | 87 | print(" gainers show top gainers in last 1h [Coingecko]") 88 | print(" losers show top losers in last 1h [Coingecko]") 89 | 90 | print( 91 | " top_sentiment show coins with most positive sentiment [Coingecko]" 92 | ) 93 | print(" top_volume show coins with highest volume [Coingecko]") 94 | 95 | print(" top_dexes show top decentralized exchanges [Coingecko]") 96 | print(" top_defi show top defi coins [Coingecko]") 97 | print(" info_defi show overview of defi [Coingecko]") 98 | print(" yield_farms show yield farms [Coingecko]") 99 | 100 | print(" stables show stable coins [Coingecko]") 101 | 102 | print(" top_nft show top nfts [Coingecko]") 103 | print(" nft_market show nft market status [Coingecko]") 104 | print(" nft_of_day show nft of a day [Coingecko]") 105 | 106 | print(" categories show top crypto categories [Coingecko]") 107 | print( 108 | " derivatives show derivatives [Coingecko] !Waiting time ~ 10-15 sec!" 109 | ) 110 | print(" indexes show indexes [Coingecko]") 111 | print(" fin_products show financial products [Coingecko]") 112 | print(" fin_platforms show financial platforms [Coingecko]") 113 | 114 | print(" btc_comp show companies that holds bitcoin [Coingecko]") 115 | print(" eth_comp show companies that holds ethereum [Coingecko]") 116 | print(" eth_holdings show eth holdings overview [Coingecko]") 117 | print(" btc_holdings show btc holdings overview [Coingecko]") 118 | 119 | print(" exchanges show info about exchanges [Coingecko]") 120 | print(" ex_rates show exchanges rates [Coingecko]") 121 | 122 | print(" ") 123 | return 124 | 125 | @staticmethod 126 | def quit(): 127 | return True 128 | 129 | @staticmethod 130 | def returner(): 131 | return False 132 | 133 | def get_view(self, an_input): 134 | (known_args, others) = self.parser.parse_known_args(an_input.split()) 135 | command = known_args.cmd 136 | if command in self.base: 137 | return self.base.get(command)() 138 | elif command in self.mapper: 139 | func = self.mapper.get(command) 140 | if func: 141 | view = create_view(name=known_args.cmd, func=func, desc="", n=others) 142 | return view 143 | else: 144 | print("Command not recognized") 145 | 146 | 147 | def main(): 148 | parser = argparse.ArgumentParser(prog="overview", add_help=False) 149 | c = Controller() 150 | choices = list(c.mapper.keys()) + list(c.base.keys()) 151 | parser.add_argument("cmd", choices=choices) 152 | print(LOGO) 153 | c.help() 154 | while True: 155 | an_input = input(f"{MOON}> (gecko_view) ") 156 | try: 157 | view = c.get_view(an_input) 158 | if view is None: 159 | continue 160 | elif isinstance(view, pd.DataFrame): 161 | print_table(view) 162 | else: 163 | return view 164 | 165 | except SystemExit: 166 | print("The command selected doesn't exist") 167 | print("\n") 168 | continue 169 | 170 | 171 | if __name__ == "__main__": 172 | main() 173 | -------------------------------------------------------------------------------- /moonbag/paprika/coinpaprika.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import logging 3 | import textwrap 4 | from moonbag.paprika._client import Client 5 | from moonbag.common.utils import wrap_headers_in_dataframe as header_wrapper 6 | 7 | 8 | class CoinPaprika(Client): 9 | def __init__(self): 10 | super().__init__() 11 | self._coins_list = self._get_coins() 12 | 13 | def _get_coins_info(self, quotes="USD"): 14 | tickers = self._get_tickers_for_all_coins(quotes) 15 | data = pd.json_normalize(tickers) 16 | try: 17 | data.columns = [ 18 | col.replace("quotes.USD.", "") for col in data.columns.tolist() 19 | ] 20 | except KeyError as e: 21 | logging.error(e) 22 | 23 | data["name"] = data["name"].apply( 24 | lambda x: "\n".join(textwrap.wrap(x, width=20)) if isinstance(x, str) else x 25 | ) 26 | return data 27 | 28 | def get_coins(self): 29 | df = pd.DataFrame(self._get_coins()) 30 | df = df[df["is_active"] != False] 31 | return df[["rank", "id", "name", "symbol", "is_new", "type"]] 32 | 33 | def get_coin_exchanges_by_id(self, coin_id="eth-ethereum"): 34 | df = pd.DataFrame(self._get_coin_exchanges_by_id(coin_id)) 35 | df["fiats"] = ( 36 | df["fiats"].copy().apply(lambda x: len([i["symbol"] for i in x if x])) 37 | ) 38 | df.columns = header_wrapper(df, n=12, replace="_") 39 | return df 40 | 41 | def get_coin_events_by_id(self, coin_id="eth-ethereum"): 42 | res = self._get_coin_events_by_id(coin_id) 43 | if not res: 44 | return pd.DataFrame() 45 | data = pd.DataFrame(res) 46 | data["description"] = data["description"].apply( 47 | lambda x: "\n".join(textwrap.wrap(x, width=30)) if isinstance(x, str) else x 48 | ) 49 | data.drop("id", axis=1, inplace=True) 50 | return data 51 | 52 | def get_coin_twitter_timeline(self, coin_id="eth-ethereum"): 53 | res = self._get_coin_twitter_timeline(coin_id) 54 | if "error" in res: 55 | print(res) 56 | return pd.DataFrame() 57 | df = pd.DataFrame(res)[ 58 | ["date", "user_name", "status", "retweet_count", "like_count"] 59 | ] 60 | 61 | df = df.applymap( 62 | lambda x: "\n".join(textwrap.wrap(x, width=100)) 63 | if isinstance(x, str) 64 | else x 65 | ) 66 | 67 | return df 68 | 69 | def get_all_contract_platforms( 70 | self, 71 | ): 72 | return pd.DataFrame(self._get_all_contract_platforms()).reset_index() 73 | 74 | def get_contract_platform(self, platform_id="eth-ethereum"): 75 | df = self._get_contract_platforms(platform_id=platform_id) 76 | return pd.DataFrame(df).reset_index() 77 | 78 | def get_coins_info(self): 79 | cols = [ 80 | "rank", 81 | "name", 82 | "symbol", 83 | "price", 84 | "volume_24h", 85 | "circulating_supply", 86 | "total_supply", 87 | "max_supply", 88 | "market_cap", 89 | "beta_value", 90 | "ath_price", 91 | ] 92 | coins = self._get_coins_info() 93 | df = pd.DataFrame(coins)[cols] 94 | df.columns = header_wrapper(df, n=12, replace="_") 95 | return df.sort_values(by="rank") 96 | 97 | def get_coins_market_info(self): 98 | cols = [ 99 | "rank", 100 | "name", 101 | "symbol", 102 | "price", 103 | "volume_24h", 104 | "market_cap_change_24h", 105 | "percent_change_1h", 106 | "percent_change_24h", 107 | "percent_change_7d", 108 | "percent_change_30d", 109 | "ath_price", 110 | "percent_from_price_ath", 111 | ] 112 | coins = self._get_coins_info() 113 | df = pd.DataFrame(coins)[cols] 114 | df.columns = header_wrapper(df, n=12, replace="_") 115 | return df.sort_values(by="rank") 116 | 117 | def get_tickers_for_coin(self, coin_id="btc-bitcoin", quotes="USD"): 118 | df = pd.json_normalize( 119 | self._get_tickers_for_coin(coin_id, quotes) 120 | ).T.reset_index() 121 | df.columns = ["Metric", "Value"] 122 | return df 123 | 124 | def global_market(self): 125 | df = pd.Series(self._get_global_market()).to_frame().reset_index() 126 | df.columns = ["Metric", "Value"] 127 | return df 128 | 129 | def get_exchanges_info(self): 130 | data = pd.json_normalize(self._get_exchanges()) 131 | try: 132 | data.columns = [ 133 | col.replace("quotes.USD.", "") for col in data.columns.tolist() 134 | ] 135 | except KeyError as e: 136 | logging.error(e) 137 | df = data[data["active"]] 138 | cols = [ 139 | "adjusted_rank", 140 | "name", 141 | "currencies", 142 | "markets", 143 | "fiats", 144 | "confidence_score", 145 | "reported_volume_24h", 146 | "reported_volume_7d", 147 | "reported_volume_30d", 148 | "sessions_per_month", 149 | ] 150 | df["fiats"] = ( 151 | df["fiats"].copy().apply(lambda x: len([i["symbol"] for i in x if x])) 152 | ) 153 | df = df[cols] 154 | df.columns = header_wrapper(df, n=12, replace="_") 155 | df = df.applymap( 156 | lambda x: "\n".join(textwrap.wrap(x, width=28)) if isinstance(x, str) else x 157 | ) 158 | return df.sort_values(by="adjusted\nrank") 159 | 160 | def get_exchanges_market(self, exchange_id="binance", quotes="USD"): 161 | data = self._get_exchange_markets(exchange_id, quotes) 162 | if "error" in data: 163 | logging.error(f"Wrong exchange_id: {exchange_id}") 164 | return pd.DataFrame() 165 | cols = [ 166 | "pair", 167 | "base_currency_name", 168 | "quote_currency_name", 169 | "market_url", 170 | "category", 171 | "reported_volume_24h_share", 172 | "trust_score", 173 | ] 174 | df = pd.DataFrame(data)[cols] 175 | df.columns = header_wrapper(df, n=15, replace="_") 176 | return df 177 | 178 | def search(self, q): 179 | results = [] 180 | data = self._search(q) 181 | for item in data: 182 | category = data[item] 183 | for r in category: 184 | results.append( 185 | { 186 | "Category": item, 187 | "Id": r.get("id"), 188 | "Name": r.get("name"), 189 | } 190 | ) 191 | return pd.DataFrame(results) 192 | 193 | def get_ohlc(self, coin_id="eth-ethereum", quotes="USD"): 194 | data = self._get_ohlc_historical(coin_id, quotes) 195 | if "error" in data: 196 | print(data["error"]) 197 | return pd.DataFrame() 198 | return pd.DataFrame(data) 199 | -------------------------------------------------------------------------------- /moonbag/onchain/ethereum/eth.py: -------------------------------------------------------------------------------- 1 | from moonbag.onchain.ethereum.utils import ( 2 | enrich_social_media, 3 | split_cols, 4 | ) 5 | from moonbag.onchain.ethereum._client import EthplorerClient 6 | import logging 7 | import pandas as pd 8 | from datetime import datetime 9 | 10 | 11 | class Eth(EthplorerClient): 12 | def __init__(self, api_key=None): 13 | super().__init__(api_key) 14 | 15 | def get_token_info(self, address): 16 | token = self._get_token_info(address) 17 | 18 | for v in [ 19 | "decimals", 20 | "issuancesCount", 21 | "lastUpdated", 22 | "image", 23 | "transfersCount", 24 | "ethTransfersCount", 25 | ]: 26 | try: 27 | token.pop(v) 28 | except KeyError as e: 29 | logging.info(e) 30 | enrich_social_media(token) 31 | df = pd.json_normalize(token) 32 | df.columns = [split_cols(x) for x in df.columns.tolist()] 33 | if "priceTs" in df: 34 | df.drop("priceTs", axis=1, inplace=True) 35 | return df.T.reset_index() 36 | 37 | def get_tx_info(self, tx_hash): 38 | tx = self._get_tx_info(tx_hash) 39 | if "error" in tx: 40 | return pd.DataFrame(tx).reset_index() 41 | try: 42 | tx.pop("logs") 43 | operations = tx.pop("operations")[0] 44 | if operations: 45 | operations.pop("addresses") 46 | token = operations.pop("tokenInfo") 47 | if token: 48 | operations["token"] = token["name"] 49 | operations["tokenAddress"] = token["address"] 50 | operations["timestamp"] = datetime.fromtimestamp( 51 | operations["timestamp"] 52 | ) 53 | tx.update(operations) 54 | tx.pop("input") 55 | df = pd.Series(tx).to_frame().reset_index() 56 | df.columns = ["Metric", "Value"] 57 | except KeyError as e: 58 | logging.info(e) 59 | return pd.DataFrame() 60 | return df 61 | 62 | def get_token_history(self, address): 63 | op = self._get_token_history(address) 64 | ops = [] 65 | if "error" in op: 66 | return pd.DataFrame(op).reset_index() 67 | try: 68 | operations = op["operations"] 69 | try: 70 | frow = operations[0]["tokenInfo"] 71 | name, addr = frow["name"], frow["address"] 72 | print(f"Fetching operations for {name}: {addr}\n") 73 | except Exception as e: 74 | logging.error(f"Didn't find data for {address}") 75 | for o in operations: 76 | o.pop("type") 77 | o.pop("tokenInfo") 78 | o["timestamp"] = datetime.fromtimestamp(o["timestamp"]) 79 | ops.append(o) 80 | except KeyError as e: 81 | logging.error(e) 82 | return pd.DataFrame(ops) # , (name, addr) 83 | 84 | def get_address_info(self, address): 85 | addr = self._get_address_info(address) 86 | if "error" in addr: 87 | return pd.DataFrame(addr).reset_index() 88 | print( 89 | f"Fetching data for {address}. This address made {addr.get('countTxs')} transactions" 90 | ) 91 | 92 | tokens = addr.pop("tokens") 93 | for token in tokens: 94 | token_info = token.pop("tokenInfo") 95 | t = { 96 | "tokenName": token_info["name"], 97 | "tokenSymbol": token_info["symbol"], 98 | "tokenAddress": token_info["address"], 99 | } 100 | token.update(t) 101 | return pd.DataFrame(tokens)[ 102 | [ 103 | "tokenName", 104 | "tokenSymbol", 105 | "tokenAddress", 106 | "rawBalance", 107 | "totalIn", 108 | "totalOut", 109 | ] 110 | ] 111 | 112 | def get_address_transactions(self, address): 113 | tx = self._get_address_transactions(address) 114 | if "error" in tx: 115 | return pd.DataFrame(tx).reset_index() 116 | try: 117 | df = pd.DataFrame(tx)[["timestamp", "from", "to", "hash"]] 118 | df["timestamp"] = df["timestamp"].apply(lambda x: datetime.fromtimestamp(x)) 119 | except Exception as e: 120 | logging.info(e) 121 | return pd.DataFrame() 122 | return df 123 | 124 | def get_address_history(self, address): 125 | tx = self._get_address_history(address) 126 | if "error" in tx: 127 | return pd.DataFrame(tx).reset_index() 128 | operations = tx.pop("operations") 129 | if operations: 130 | for operation in operations: 131 | token = operation.pop("tokenInfo") 132 | if token: 133 | operation["token"] = token["name"] 134 | operation["tokenAddress"] = token["address"] 135 | operation["timestamp"] = datetime.fromtimestamp(operation["timestamp"]) 136 | 137 | df = pd.DataFrame(operations) 138 | return df[["timestamp", "from", "to", "transactionHash"]] 139 | 140 | def get_top_token_holders(self, address): 141 | d = self._get_top_token_holders(address) 142 | if "error" in d: 143 | logging.info(d["error"]) 144 | return pd.DataFrame(d).reset_index() 145 | return pd.DataFrame(d["holders"]).reset_index() 146 | 147 | def get_top_tokens(self): 148 | t = self._get_top_tokens() 149 | if "error" in t: 150 | return pd.DataFrame(t).reset_index() 151 | tokens = t["tokens"] 152 | df = pd.DataFrame(tokens) 153 | cols = [ 154 | "name", 155 | "symbol", 156 | "txsCount", 157 | "transfersCount", 158 | "holdersCount", 159 | "address", 160 | "twitter", 161 | "coingecko", 162 | ] 163 | return df[cols] 164 | 165 | def _get_token_price_history_helper(self, address): 166 | hist = self._get_token_price_history_grouped(address) 167 | if "error" in hist: 168 | return pd.DataFrame(hist).reset_index() 169 | data = hist["history"] 170 | current = data.pop("current") 171 | txs = [] 172 | for i in data["countTxs"]: 173 | txs.append({"ts": i["ts"], "cnt": i["cnt"]}) 174 | txs_df = pd.DataFrame(txs) 175 | txs_df["ts"] = txs_df["ts"].apply(lambda x: datetime.fromtimestamp(x)) 176 | prices_df = pd.DataFrame(data["prices"]) 177 | prices_df["ts"] = prices_df["ts"].apply(lambda x: datetime.fromtimestamp(x)) 178 | prices_df.drop("tmp", axis=1, inplace=True) 179 | return prices_df, txs_df 180 | 181 | def get_token_historical_price(self, address): 182 | try: 183 | df = self._get_token_price_history_helper(address)[0] 184 | except Exception as e: 185 | logging.info(e) 186 | return pd.DataFrame() 187 | return df 188 | 189 | def get_token_historical_txs(self, address): 190 | try: 191 | df = self._get_token_price_history_helper(address)[1] 192 | except Exception as e: 193 | logging.info(e) 194 | return pd.DataFrame() 195 | return df 196 | -------------------------------------------------------------------------------- /moonbag/discover/reddit_client/reddit.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | import pandas as pd 4 | import textwrap 5 | from moonbag.discover.reddit_client._client import RedditClient, praw 6 | from moonbag.common.utils import created_date 7 | import datetime 8 | from typing import Union 9 | 10 | CRYPTO_SUBREDDITS = [ 11 | "CryptoCurrency", 12 | "CryptoMoonShots", 13 | "SatoshiStreetBets", 14 | "AltStreetBets", 15 | "altcoin", 16 | "dogecoin", 17 | "ethtrader", 18 | "bitcoin", 19 | "btc", 20 | "ethereum", 21 | "algotrading", 22 | ] 23 | 24 | 25 | class Reddit(RedditClient): 26 | subreddits = [ 27 | "CryptoCurrency", 28 | "CryptoMoonShots", 29 | "SatoshiStreetBets", 30 | "AltStreetBets", 31 | "bitcoin", 32 | ] 33 | 34 | def get_subreddit(self, subreddit: str): 35 | sub = self._get_subbreddit(subreddit) 36 | return dict( 37 | id=sub.id, 38 | title=sub.title, 39 | name=sub.name, 40 | subscribers=sub.subscribers, 41 | created_date=created_date(sub.created), 42 | url=sub.url, 43 | ) 44 | 45 | def get_subreddits(self, subreddits: list): 46 | return (self.get_subreddit(sub) for sub in subreddits) 47 | 48 | def get_submission(self, submission_id): 49 | s = self.client.submission(id=submission_id) 50 | return dict( 51 | created=created_date(s.created), 52 | score=s.score, 53 | upvotes=s.ups, 54 | shortlink=s.shortlink, 55 | title=s.title, 56 | comments=s.num_comments, 57 | subreddit_name_prefixed=s.subreddit_name_prefixed, 58 | ) 59 | 60 | def get_submissions(self, submission_ids: list): 61 | return (self._get_submission(submission_id) for submission_id in submission_ids) 62 | 63 | def get_submissions_for_subreddits(self, subreddits: Union[str, list], kind="top"): 64 | # kind = "top", "controversial", "hot" 65 | results = [] 66 | if isinstance(subreddits, str): 67 | subreddits = [subreddits] 68 | for subreddit in subreddits: 69 | sub = self._get_subbreddit(subreddit) 70 | print(f"Fetching data for subreddit: {sub}") 71 | if kind in ["top", "controversial"]: 72 | top = getattr(sub, kind)(time_filter="day") 73 | else: 74 | top = getattr(sub, kind) 75 | try: 76 | for s in top: 77 | results.append( 78 | dict( 79 | created=created_date(s.created), 80 | score=s.score, 81 | upvotes=s.ups, 82 | shortlink=s.shortlink, 83 | title=s.title, 84 | comments=s.num_comments, 85 | subreddit_name_prefixed=s.subreddit_name_prefixed, 86 | ) 87 | ) 88 | except Exception as e: 89 | logging.info(e) 90 | 91 | try: 92 | df = pd.DataFrame(results).sort_values(by="comments", ascending=False) 93 | df["title"] = df["title"].apply( 94 | lambda x: "\n".join(textwrap.wrap(x, width=52)) 95 | if isinstance(x, str) 96 | else x 97 | ) 98 | return df[df["comments"] >= 50] 99 | except KeyError as e: 100 | logging.info(e) 101 | print(f"Couldn't find data for {subreddits}") 102 | return pd.DataFrame() 103 | 104 | def get_comments_for_submission(self, submission_id): 105 | submission = self._get_submission(submission_id) 106 | data = [ 107 | dict( 108 | id=c.id, 109 | submissionId=c.submission.id, 110 | subredditId=c.subreddit_id, 111 | author=c.author.name if c.author else None, 112 | body=c.body, 113 | score=c.score, 114 | upvotes=c.ups, 115 | created=created_date(c.created), 116 | ) 117 | for c in submission.comments 118 | if not isinstance(c, praw.reddit.models.MoreComments) 119 | ] 120 | return pd.DataFrame(data) 121 | 122 | def discover_top_submissions(self, kind="top"): 123 | return self.get_submissions_for_subreddits(self.subreddits, kind) 124 | 125 | def search(self, query, data_type="submission"): 126 | now = (datetime.datetime.now() - datetime.timedelta(days=7)).timestamp() 127 | results = self._search_psaw_data( 128 | q=query, 129 | data_type=data_type, 130 | size=1000, 131 | sort_type="score", 132 | after=int(now), 133 | sort="desc", 134 | ) 135 | data = results["data"] 136 | results = [] 137 | 138 | if data_type.lower() == "comment": 139 | for s in data: 140 | results.append( 141 | dict( 142 | created=created_date(s.get("created_utc")), 143 | # comments=s.get('num_comments'), 144 | score=s.get("score"), 145 | subreddit=s.get("subreddit"), 146 | title=s.get("body"), 147 | ) 148 | ) 149 | else: 150 | for s in data: 151 | results.append( 152 | dict( 153 | created=created_date(s.get("created_utc")), 154 | comments=s.get("num_comments"), 155 | score=s.get("score"), 156 | subreddit=s.get("subreddit"), 157 | title=s.get("title"), 158 | ) 159 | ) 160 | if not results or results == []: 161 | print("No results found") 162 | return pd.DataFrame() 163 | else: 164 | try: 165 | df = pd.DataFrame(results).sort_values(by="score", ascending=False) 166 | df["title"] = df["title"].apply( 167 | lambda x: "\n".join(textwrap.wrap(x, width=76)) 168 | if isinstance(x, str) 169 | else x 170 | ) 171 | except KeyError as e: 172 | print(e) 173 | return pd.DataFrame() 174 | return df.sort_values(by="created", ascending=False) 175 | 176 | def get_popular_submissions(self): 177 | results = [] 178 | for sub in CRYPTO_SUBREDDITS: 179 | print(f"Searching data for subreddit: {sub}") 180 | time.sleep(0.3) 181 | subm = self.psaw.search_submissions( 182 | subreddit=sub, limit=1000, score=">50", after="7d" 183 | ) 184 | for s in subm: 185 | results.append( 186 | dict( 187 | created=created_date(s.created_utc), 188 | comments=s.num_comments, 189 | score=s.score, 190 | subreddit=s.subreddit, 191 | title=s.title, 192 | ) 193 | ) 194 | 195 | df = pd.DataFrame(results).sort_values(by="score", ascending=False) 196 | df["title"] = df["title"].apply( 197 | lambda x: "\n".join(textwrap.wrap(x, width=76)) if isinstance(x, str) else x 198 | ) 199 | return df 200 | -------------------------------------------------------------------------------- /moonbag/discover/defi/graph.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from moonbag.common.utils import created_date 3 | import requests 4 | import pandas as pd 5 | from moonbag.common.keys import BIT_QUERY_API 6 | import logging 7 | 8 | 9 | class GraphClient: 10 | UNI = "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2" 11 | BQ = "https://graphql.bitquery.io" 12 | CMP = "https://api.thegraph.com/subgraphs/name/graphprotocol/compound-v2" 13 | 14 | @staticmethod 15 | def run_query( 16 | url, query 17 | ): # A simple function to use requests.post to make the API call. Note the json= section. 18 | headers = {"x-api-key" : BIT_QUERY_API} 19 | request = requests.post(url, json={"query": query}, 20 | headers=headers 21 | ) 22 | if request.status_code == 200: 23 | return request.json()["data"] 24 | else: 25 | print( 26 | "Query failed to run by returning code of {}.".format( 27 | request.status_code 28 | ) 29 | ) 30 | if request.status_code == 403: 31 | print("Please visit https://graphql.bitquery.io/ and generate you free api key") 32 | 33 | def get_uni_tokens(self, skip=0): 34 | q = ( 35 | """ 36 | { 37 | tokens(first: 1000, skip:%s) { 38 | symbol 39 | name 40 | tradeVolumeUSD 41 | totalLiquidity 42 | txCount 43 | } 44 | } 45 | """ 46 | % skip 47 | ) 48 | 49 | data = self.run_query(self.UNI, q) 50 | if not data: 51 | print("Couldnt find data") 52 | return pd.DataFrame() 53 | 54 | return pd.DataFrame(data["tokens"]) 55 | 56 | def get_dex_trades_by_protocol(self): 57 | q = """ 58 | { 59 | ethereum { 60 | dexTrades(options: {limit: 100, desc: "count"}) { 61 | protocol 62 | count 63 | tradeAmount(in: USD) 64 | } 65 | } 66 | } 67 | 68 | """ 69 | data = self.run_query(self.BQ, q) 70 | if not data: 71 | print("Couldnt find data") 72 | return pd.DataFrame() 73 | return pd.DataFrame(data["ethereum"]["dexTrades"]) 74 | 75 | def get_dex_trades_monthly(self): 76 | q = """ 77 | { 78 | ethereum { 79 | dexTrades(options: {limit: 1000, desc: ["count","protocol", "date.year","date.month"]}) { 80 | protocol 81 | count 82 | tradeAmount(in: USD) 83 | date { 84 | year 85 | month 86 | } 87 | } 88 | } 89 | } 90 | 91 | """ 92 | data = self.run_query(self.BQ, q) 93 | return pd.json_normalize(data["ethereum"]["dexTrades"]) 94 | 95 | def get_uniswap_pool_lastly_added( 96 | self, last_days=14, min_volume=100, min_liquidity=0, min_tx=100 97 | ): 98 | days = int( 99 | (datetime.datetime.now() - datetime.timedelta(days=last_days)).timestamp() 100 | ) 101 | q = """ 102 | { 103 | pairs(first: 1000, where: {createdAtTimestamp_gt: "%s", volumeUSD_gt: "%s", reserveUSD_gt: "%s", txCount_gt: "%s" }, 104 | orderBy: createdAtTimestamp, orderDirection: desc) { 105 | 106 | token0 { 107 | symbol 108 | name 109 | } 110 | token1 { 111 | symbol 112 | name 113 | } 114 | reserveUSD 115 | volumeUSD 116 | createdAtTimestamp 117 | totalSupply 118 | txCount 119 | 120 | } 121 | } 122 | 123 | """ % ( 124 | days, 125 | min_volume, 126 | min_liquidity, 127 | min_tx, 128 | ) 129 | data = self.run_query(self.UNI, q) 130 | if not data: 131 | print("Couldnt find data") 132 | return pd.DataFrame() 133 | df = pd.json_normalize(data["pairs"]) 134 | df["createdAtTimestamp"] = df["createdAtTimestamp"].apply( 135 | lambda x: created_date(int(x)) 136 | ) 137 | df["pair"] = df["token0.symbol"] + "/" + df["token1.symbol"] 138 | return df[ 139 | [ 140 | "createdAtTimestamp", 141 | "pair", 142 | "token0.name", 143 | "token1.name", 144 | "volumeUSD", 145 | "txCount", 146 | "totalSupply", 147 | "reserveUSD", 148 | ] 149 | ] 150 | 151 | def get_uniswap_pools_by_volume(self): 152 | q = """ 153 | { 154 | pairs(first: 1000, where: {reserveUSD_gt: "1000", volumeUSD_gt: "10000"}, 155 | orderBy: volumeUSD, orderDirection: desc) { 156 | 157 | token0 { 158 | symbol 159 | name 160 | } 161 | token1 { 162 | symbol 163 | name 164 | } 165 | volumeUSD 166 | txCount 167 | 168 | } 169 | } 170 | 171 | """ 172 | data = self.run_query(self.UNI, q) 173 | if not data: 174 | print("Couldnt find data") 175 | return pd.DataFrame() 176 | df = pd.json_normalize(data["pairs"]) 177 | 178 | df = df[df["token0.name"] != "You don't blacklist delta.financial"] 179 | df = df[df["token1.name"] != "You don't blacklist delta.financial"] 180 | return df[ 181 | [ 182 | "token0.name", 183 | "token0.symbol", 184 | "token1.name", 185 | "token1.symbol", 186 | "volumeUSD", 187 | "txCount", 188 | ] 189 | ] 190 | 191 | def get_uniswap_stats(self): 192 | q = """ 193 | { 194 | uniswapFactory(id: "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"){ 195 | totalVolumeUSD 196 | totalLiquidityUSD 197 | pairCount 198 | txCount 199 | totalLiquidityUSD 200 | totalLiquidityETH 201 | 202 | } 203 | } 204 | """ 205 | data = self.run_query(self.UNI, q) 206 | if not data: 207 | print("Couldnt find data") 208 | return pd.DataFrame() 209 | return pd.Series(data["uniswapFactory"]).reset_index() 210 | 211 | def get_compound_markets(self): 212 | q = """ 213 | { 214 | markets(first: 100) { 215 | name 216 | symbol 217 | borrowRate 218 | collateralFactor 219 | exchangeRate 220 | supplyRate 221 | totalBorrows 222 | totalSupply 223 | underlyingPriceUSD 224 | } 225 | } 226 | 227 | 228 | """ 229 | 230 | data = self.run_query(self.CMP, q) 231 | if not data: 232 | print("Couldnt find data") 233 | return pd.DataFrame() 234 | return pd.DataFrame(data["markets"]) 235 | 236 | def get_last_swaps_uni(self): 237 | q = """ 238 | { 239 | swaps(first: 1000, orderBy: timestamp, orderDirection: desc) { 240 | timestamp 241 | pair { 242 | token0 { 243 | symbol 244 | } 245 | token1 { 246 | symbol 247 | } 248 | } 249 | amountUSD 250 | } 251 | } 252 | 253 | """ 254 | 255 | data = self.run_query(self.UNI, q) 256 | if not data: 257 | print("Couldnt find data") 258 | return pd.DataFrame() 259 | df = pd.json_normalize(data["swaps"]) 260 | 261 | df["timestamp"] = df["timestamp"].apply(lambda x: created_date(int(x))) 262 | df.columns = ["amountUSD", "timestamp", "token0", "token1"] 263 | return df[["timestamp", "token0", "token1", "amountUSD", "timestamp"]] 264 | -------------------------------------------------------------------------------- /moonbag/gecko/coin_menu.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from moonbag.gecko.gecko import get_coin_list, Coin 3 | import logging 4 | from moonbag.common import LOGO, MOON, print_table 5 | from typing import List 6 | import textwrap 7 | from inspect import signature 8 | import difflib 9 | import pandas as pd 10 | from argparse import ArgumentError 11 | 12 | logger = logging.getLogger("gecko-menu") 13 | 14 | 15 | class Controller: 16 | def __init__(self): 17 | self.parser = argparse.ArgumentParser(prog="coin", add_help=False) 18 | self.parser.add_argument("cmd") 19 | self.base_commands = ["help", "exit", "quit", "r", "q"] 20 | self.mapper = { 21 | "similar": self.find_similar_coins, 22 | "load": self.load_coin, 23 | "web": self.show_web, 24 | "info": self.show_coin_base_info, 25 | "devs": self.show_developers, 26 | "market": self.show_market, 27 | "social": self.show_socials, 28 | "ath": self.show_ath, 29 | "atl": self.show_atl, 30 | "coinlist": self.show_list_of_coins, 31 | "explorers": self.show_bcexplores, 32 | } 33 | 34 | self.coin = None 35 | 36 | @staticmethod 37 | def help(): 38 | print("Main commands:") 39 | print(" help show help") 40 | print(" r return to previous menu") 41 | print(" quit quit program") 42 | print("") 43 | print("Coin View [Coingecko]") 44 | print( 45 | " similar don't remember symbol of coin ? Look for closest matches [Coingecko]" 46 | ) 47 | print(" load load coin, example: 'load -c uniswap' [Coingecko]") 48 | print(" coinlist show list of all coins available in [Coingecko]") 49 | print(" info show info about loaded coin [Coingecko]") 50 | print(" market show market info about loaded coin [Coingecko]") 51 | print( 52 | " devs show development information about loaded coins [Coingecko]" 53 | ) 54 | print(" ath show info all time high of loaded coin [Coingecko]") 55 | print(" atl show info all time low of loaded coin [Coingecko]") 56 | print(" web show web pages founded for loaded coin [Coingecko]") 57 | print( 58 | " explorers show blockchain explorers links for loaded coin [Coingecko]" 59 | ) 60 | return 61 | 62 | def load_coin(self, args: List[str]): 63 | """U can use id or symbol of coins""" 64 | parser = argparse.ArgumentParser( 65 | prog="load", 66 | add_help=True, 67 | description="Load coin from coingecko\n If you not sure what is the symbol or id of coin use method coinlist", 68 | ) 69 | parser.add_argument( 70 | "-c", 71 | "--coin", 72 | help="Coin to get", 73 | dest="coin", 74 | required=True, 75 | type=str, 76 | ) 77 | 78 | if not args: 79 | return 80 | 81 | if "-" not in args[0]: 82 | args.insert(0, "-c") 83 | 84 | parsy, _ = parser.parse_known_args(args) 85 | if not parsy: 86 | return 87 | 88 | try: 89 | self.coin = Coin(parsy.coin) 90 | except ValueError as e: 91 | print(f"{e}, To check list of coins use command: coinlist ") 92 | return 93 | 94 | else: 95 | print(f"Coin loaded {self.coin.coin_symbol}") 96 | 97 | @property 98 | def _is_loaded(self): 99 | if self.coin is None: 100 | print("You didn't load a coin, plase first use load -c symbol to load coin") 101 | return False 102 | return True 103 | 104 | def show_coin_base_info(self): 105 | if self._is_loaded: 106 | df = self.coin.base_info.reset_index() 107 | df = df.applymap( 108 | lambda x: "\n".join(textwrap.wrap(x, width=120)) 109 | if isinstance(x, str) 110 | else x 111 | ) 112 | df.columns = ["Metric", "Value"] 113 | print_table(df) 114 | 115 | @staticmethod 116 | def show_list_of_coins(): 117 | print_table(get_coin_list(), "plain") 118 | 119 | def show_scores( 120 | self, 121 | ): 122 | if self._is_loaded: 123 | df = self.coin.scores 124 | df = df.applymap( 125 | lambda x: "\n".join(textwrap.wrap(x, width=200)) 126 | if isinstance(x, str) 127 | else x 128 | ) 129 | df.columns = ["Metric", "Value"] 130 | print_table(df) 131 | 132 | def show_market(self): 133 | if self._is_loaded: 134 | df = self.coin.market_data 135 | print_table(df) 136 | 137 | def show_atl( 138 | self, 139 | ): 140 | if self._is_loaded: 141 | print_table(self.coin.all_time_low) 142 | 143 | def show_ath( 144 | self, 145 | ): 146 | if self._is_loaded: 147 | df = self.coin.all_time_high 148 | print_table(df) 149 | 150 | def show_developers( 151 | self, 152 | ): 153 | if self._is_loaded: 154 | print_table(self.coin.developers_data) 155 | 156 | def show_bcexplores( 157 | self, 158 | ): 159 | if self._is_loaded: 160 | print_table(self.coin.blockchain_explorers) 161 | 162 | def show_socials( 163 | self, 164 | ): 165 | if self._is_loaded: 166 | print_table(self.coin.social_media) 167 | 168 | def show_web( 169 | self, 170 | ): 171 | if self._is_loaded: 172 | print_table(self.coin.websites) 173 | 174 | @staticmethod 175 | def find_similar_coins(args): 176 | parser = argparse.ArgumentParser( 177 | prog="similar", 178 | add_help=True, 179 | description="Find similar coins", 180 | ) 181 | parser.add_argument( 182 | "-c", 183 | "--coin", 184 | help="Symbol/Name of Coin", 185 | dest="symbol", 186 | required=True, 187 | type=str, 188 | ) 189 | 190 | if not args: 191 | print("You didn't pass coin symbol. Please use similar -c symbol") 192 | return 193 | 194 | parsy, others = parser.parse_known_args(args) 195 | if not parsy or parsy.symbol is None: 196 | return 197 | 198 | coins = get_coin_list().id.to_list() 199 | sim = difflib.get_close_matches(parsy.symbol, coins, 10) 200 | df = pd.Series(sim).to_frame().reset_index() 201 | df.columns = ["Index", "Name"] 202 | print_table(df) 203 | 204 | 205 | def main(): 206 | c = Controller() 207 | choices = list(c.mapper.keys()) + c.base_commands 208 | 209 | parser = argparse.ArgumentParser(prog="coin", add_help=False) 210 | parser.add_argument("cmd", choices=choices) 211 | print(LOGO) 212 | c.help() 213 | while True: 214 | an_input = input(f"{MOON}> (gecko_coin) ") 215 | 216 | try: 217 | parsy, others = parser.parse_known_args(an_input.split()) 218 | cmd = parsy.cmd 219 | if cmd == "help": 220 | c.help() 221 | elif cmd in ["exit", "quit", "q"]: 222 | return True 223 | elif cmd == "r": 224 | return False 225 | 226 | view = c.mapper.get(cmd) 227 | if c.coin: 228 | print("\n>>> Loaded coin: ", c.coin, " <<<") 229 | if view is None: 230 | continue 231 | elif callable( 232 | view 233 | ): # If function takes params return func(args), else func() 234 | if len(signature(view).parameters) > 0: 235 | view(others) 236 | else: 237 | view() 238 | except ArgumentError: 239 | print("The command selected doesn't exist") 240 | print("\n") 241 | continue 242 | except SystemExit: 243 | print("\n") 244 | continue 245 | 246 | 247 | if __name__ == "__main__": 248 | main() 249 | -------------------------------------------------------------------------------- /moonbag/onchain/ethereum/menu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import os 4 | import argparse 5 | import logging 6 | from moonbag.common import LOGO, MOON, print_table 7 | from argparse import ArgumentError 8 | from inspect import signature 9 | from moonbag.common.utils import MoonParser 10 | from moonbag.onchain.ethereum.eth import Eth 11 | 12 | logger = logging.getLogger("ethereum-menu") 13 | 14 | 15 | class Controller: 16 | def __init__(self): 17 | self.client = Eth() 18 | self.parser = argparse.ArgumentParser(prog="eth", add_help=False) 19 | self.parser.add_argument("cmd") 20 | self.base_commands = ["help", "exit", "quit", "r", "q"] 21 | self.mapper = { 22 | "token_info": self.show_token_info, 23 | "tx_info": self.show_tx_info, 24 | "address_info": self.show_address_info, 25 | "address_tx": self.show_address_tx, 26 | "address_hist": self.show_address_hist, 27 | "top_tokens": self.show_top_tokens, 28 | "token_holders": self.show_top_token_holders, 29 | "token_price": self.show_token_price, 30 | "token_hist": self.show_token_history, 31 | "token_txs": self.show_token_txs, 32 | } 33 | 34 | @staticmethod 35 | def help(): 36 | print("Main commands:") 37 | print(" help show help") 38 | print(" r return to previous menu") 39 | print(" quit quit program") 40 | print("") 41 | print("Ethplorer ") 42 | print(" token_info show info about erc20 token [Ethplorer]") 43 | print( 44 | " tx_info show info about transaction on ethereum blockchain [Ethplorer]" 45 | ) 46 | print(" address_info show info about ethereum address [Ethplorer]") 47 | print(" address_tx show ethereum address transactions [Ethplorer]") 48 | print(" top_tokens show most popular coin on ethplorer [Ethplorer]") 49 | 50 | print(" token_holders show info about token holders [Ethplorer]") 51 | 52 | print(" token_price show info about historical token prices [Ethplorer]") 53 | print(" token_hist show historical info about erc20 token [Ethplorer]") 54 | print( 55 | " token_txs show info about historical token transactions [Ethplorer]" 56 | ) 57 | 58 | print(" ") 59 | return 60 | 61 | def show_token_info(self, args): 62 | parser = MoonParser( 63 | prog="token info", 64 | add_help=True, 65 | description="get token information", 66 | ) 67 | parser.add_address_argument() 68 | parsy, _ = parser.parse_known_args(args) 69 | try: 70 | prices = self.client.get_token_info(parsy.address) 71 | except ValueError as e: 72 | print(f"{e}") 73 | return 74 | print_table(prices) 75 | 76 | def show_token_history(self, args): 77 | parser = MoonParser( 78 | prog="token history", 79 | add_help=True, 80 | description="get token history", 81 | ) 82 | parser.add_address_argument() 83 | parsy, _ = parser.parse_known_args(args) 84 | try: 85 | prices = self.client.get_token_history(parsy.address) 86 | except ValueError as e: 87 | print(f"{e}") 88 | return 89 | print_table(prices) 90 | 91 | def show_tx_info(self, args): 92 | parser = MoonParser( 93 | prog="ethereum transaction info", 94 | add_help=True, 95 | description="get transaction details", 96 | ) 97 | parser.add_address_argument(help="Ethereum transaction hash") 98 | parsy, _ = parser.parse_known_args(args) 99 | try: 100 | data = self.client.get_tx_info(parsy.address) 101 | except ValueError as e: 102 | print(f"{e}") 103 | return 104 | print_table(data) 105 | 106 | def show_address_info(self, args): 107 | parser = MoonParser( 108 | prog="ethereum address info", 109 | add_help=True, 110 | description="get address details", 111 | ) 112 | parser.add_address_argument(help="Ethereum address") 113 | parsy, _ = parser.parse_known_args(args) 114 | try: 115 | data = self.client.get_address_info(parsy.address) 116 | except ValueError as e: 117 | print(f"{e}") 118 | return 119 | print_table(data) 120 | 121 | def show_address_tx(self, args): 122 | parser = MoonParser( 123 | prog="ethereum address transactions info", 124 | add_help=True, 125 | description="show address transactions details", 126 | ) 127 | parser.add_address_argument(help="Ethereum address") 128 | parsy, _ = parser.parse_known_args(args) 129 | try: 130 | data = self.client.get_address_transactions(parsy.address) 131 | except ValueError as e: 132 | print(f"{e}") 133 | return 134 | print_table(data) 135 | 136 | def show_address_hist(self, args): 137 | parser = MoonParser( 138 | prog="ethereum address history", 139 | add_help=True, 140 | description="show address history", 141 | ) 142 | parser.add_address_argument(help="Ethereum address") 143 | parsy, _ = parser.parse_known_args(args) 144 | try: 145 | data = self.client.get_address_history(parsy.address) 146 | except ValueError as e: 147 | print(f"{e}") 148 | return 149 | print_table(data) 150 | 151 | def show_top_token_holders(self, args): 152 | parser = MoonParser( 153 | prog="ethereum token holders", 154 | add_help=True, 155 | description="show top token holders", 156 | ) 157 | parser.add_address_argument(help="Ethereum token address") 158 | parsy, _ = parser.parse_known_args(args) 159 | try: 160 | data = self.client.get_top_token_holders(parsy.address) 161 | except ValueError as e: 162 | print(f"{e}") 163 | return 164 | print_table(data) 165 | 166 | def show_top_tokens(self, args): 167 | parser = MoonParser( 168 | prog="ethereum top tokens", 169 | add_help=True, 170 | description="show top tokens", 171 | ) 172 | parsy, _ = parser.parse_known_args(args) 173 | try: 174 | data = self.client.get_top_tokens() 175 | except ValueError as e: 176 | print(f"{e}") 177 | return 178 | print_table(data) 179 | 180 | def show_token_price(self, args): 181 | parser = MoonParser( 182 | prog="token price", 183 | add_help=True, 184 | description="show token prices", 185 | ) 186 | parser.add_address_argument(help="Ethereum token address") 187 | parsy, _ = parser.parse_known_args(args) 188 | try: 189 | data = self.client.get_token_historical_price(parsy.address) 190 | except ValueError as e: 191 | print(f"{e}") 192 | return 193 | print_table(data) 194 | 195 | def show_token_txs(self, args): 196 | parser = MoonParser( 197 | prog="token transactions", 198 | add_help=True, 199 | description="show token transactions", 200 | ) 201 | parser.add_address_argument(help="Ethereum token address") 202 | parsy, _ = parser.parse_known_args(args) 203 | try: 204 | data = self.client.get_token_historical_txs(parsy.address) 205 | except ValueError as e: 206 | print(f"{e}") 207 | return 208 | print_table(data) 209 | 210 | 211 | def main(): 212 | c = Controller() 213 | choices = c.base_commands + list(c.mapper.keys()) 214 | if sys.platform == "win32": 215 | os.system("") 216 | 217 | parser = argparse.ArgumentParser(prog="ethereum", add_help=False) 218 | parser.add_argument("cmd", choices=choices) 219 | 220 | print(LOGO) 221 | c.help() 222 | while True: 223 | 224 | an_input = input(f"{MOON}> (ethereum) ") 225 | try: 226 | parsy, others = parser.parse_known_args(an_input.split()) 227 | cmd = parsy.cmd 228 | 229 | if cmd == "help": 230 | c.help() 231 | elif cmd in ["exit", "quit", "q"]: 232 | return True 233 | elif cmd == "r": 234 | return False 235 | 236 | view = c.mapper.get(cmd) 237 | if view is None: 238 | continue 239 | elif callable( 240 | view 241 | ): # If function takes params return func(args), else func() 242 | if len(signature(view).parameters) > 0: 243 | view(others) 244 | else: 245 | view() 246 | 247 | except ArgumentError: 248 | print("The command selected doesn't exist") 249 | print("") 250 | continue 251 | 252 | except SystemExit: 253 | print("") 254 | print("") 255 | continue 256 | -------------------------------------------------------------------------------- /moonbag/paprika/menu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import os 4 | import time 5 | import argparse 6 | import logging 7 | from moonbag.common import LOGO, MOON, print_table 8 | from argparse import ArgumentError 9 | from inspect import signature 10 | from moonbag.common.utils import MoonParser 11 | from moonbag.paprika.coinpaprika import CoinPaprika 12 | 13 | logger = logging.getLogger("paprika-menu") 14 | 15 | 16 | class Controller: 17 | def __init__(self): 18 | self.client = CoinPaprika() 19 | self.parser = argparse.ArgumentParser(prog="coin", add_help=False) 20 | self.parser.add_argument("cmd") 21 | self.base_commands = ["help", "exit", "quit", "r", "q"] 22 | self.mapper = { 23 | "coins_list": self.show_coins_list, 24 | "coins_info": self.show_coins_info, 25 | "coins_market": self.show_coins_market, 26 | "exchanges_info": self.show_exchanges_info, 27 | "exchanges_market": self.show_exchanges_market, 28 | "coin_exchanges": self.show_coin_exchanges, 29 | "coin_twitter": self.show_coin_twitter, 30 | "coin_events": self.show_coin_events, 31 | "platforms": self.show_platforms, 32 | "contracts": self.show_contracts, 33 | "global_info": self.show_global_market, 34 | "coin_ohlc": self.show_ohlc, 35 | "search": self.search, 36 | } 37 | 38 | @staticmethod 39 | def help(): 40 | print("Main commands:") 41 | print(" help show help") 42 | print(" r return to previous menu") 43 | print(" quit quit program") 44 | print("") 45 | print("Coinpaprika ") 46 | print(" global_info show global info about crypto market [Coinpaprika]") 47 | print(" search try to find coin, exchange [Coinpaprika]") 48 | print(" coins_list show all coins available [Coinpaprika]") 49 | print(" coins_info show coins base information [Coinpaprika]") 50 | print(" coins_market show coins market information [Coinpaprika]") 51 | 52 | print( 53 | " exchanges_info show base information about exchanges [Coinpaprika]" 54 | ) 55 | print( 56 | " exchanges_market show base information about exchanges [Coinpaprika]" 57 | ) 58 | 59 | print( 60 | " platforms show all platforms with smart contracts [Coinpaprika]" 61 | ) 62 | print( 63 | " contracts show all contracts for given platform platforms. Defautlt eth-ethereum [Coinpaprika]" 64 | ) 65 | 66 | print( 67 | " coin_exchanges show all exchanges for given coin. Use coin_id as input [Coinpaprika]" 68 | ) 69 | print( 70 | " coin_events show all event for given coin Use coin_id as input [Coinpaprika]" 71 | ) 72 | print( 73 | " coin_twitter show twitter timeline for given coin. Use coin_id as input [Coinpaprika]" 74 | ) 75 | 76 | print( 77 | " coin_ohlc show coin open-high-low-close prices data for last year. Use coin_id as input [Coinpaprika]" 78 | ) 79 | print(" ") 80 | return 81 | 82 | def show_coins_info(self, args): 83 | parser = MoonParser( 84 | prog="coins info", 85 | add_help=True, 86 | description="get coin information", 87 | ) 88 | parser.add_limit_argument(default=500, help="Number of coins", dest="limit") 89 | parsy, _ = parser.parse_known_args(args) 90 | try: 91 | prices = self.client.get_coins_info() 92 | except ValueError as e: 93 | print(f"{e}") 94 | return 95 | print_table(prices.head(parsy.limit)) 96 | 97 | def show_coins_market(self, args): 98 | parser = MoonParser( 99 | prog="coins market info", 100 | add_help=True, 101 | description="get market info for all coins", 102 | ) 103 | parser.add_limit_argument(default=500, help="Number of coins", dest="limit") 104 | parsy, _ = parser.parse_known_args(args) 105 | try: 106 | prices = self.client.get_coins_market_info() 107 | except ValueError as e: 108 | print(f"{e}") 109 | return 110 | print_table(prices.head(parsy.limit)) 111 | 112 | def show_coins_list(self, args): 113 | parser = MoonParser( 114 | prog="all coins", 115 | add_help=True, 116 | description="get list of coins", 117 | ) 118 | parser.add_limit_argument(default=500, help="Number of coins", dest="limit") 119 | parsy, _ = parser.parse_known_args(args) 120 | try: 121 | prices = self.client.get_coins() 122 | except ValueError as e: 123 | print(f"{e}") 124 | return 125 | print_table(prices.head(parsy.limit)) 126 | 127 | def show_exchanges_info(self, args): 128 | parser = MoonParser( 129 | prog="exchanges info", 130 | add_help=True, 131 | description="show exchanges information", 132 | ) 133 | parser.add_limit_argument(default=100, help="Number of records", dest="limit") 134 | parsy, _ = parser.parse_known_args(args) 135 | try: 136 | prices = self.client.get_exchanges_info() 137 | except ValueError as e: 138 | print(f"{e}") 139 | return 140 | print_table(prices.head(parsy.limit)) 141 | 142 | def show_exchanges_market(self, args): 143 | parser = MoonParser( 144 | prog="exchanges info", 145 | add_help=True, 146 | description="show exchanges information", 147 | ) 148 | 149 | parser.add_exchange_argument(default="binance", required=False, dest="exchange") 150 | parser.add_limit_argument(default=100, help="Number of records", dest="limit") 151 | parsy, _ = parser.parse_known_args(args) 152 | try: 153 | prices = self.client.get_exchanges_market(exchange_id=parsy.exchange) 154 | except ValueError as e: 155 | print(f"{e}") 156 | return 157 | print_table(prices.head(parsy.limit)) 158 | 159 | def _show_coin_related(self, args): 160 | parser = MoonParser( 161 | prog="exchanges info", 162 | add_help=True, 163 | description="show exchanges information", 164 | ) 165 | 166 | parser.add_coin_argument() 167 | parsy, _ = parser.parse_known_args(args) 168 | 169 | return parsy 170 | 171 | def show_coin_exchanges(self, args): 172 | parsy = self._show_coin_related(args) 173 | 174 | try: 175 | prices = self.client.get_coin_exchanges_by_id(coin_id=parsy.symbol) 176 | except ValueError as e: 177 | print(f"{e}") 178 | return 179 | print_table(prices) 180 | 181 | def show_coin_events(self, args): 182 | parsy = self._show_coin_related(args) 183 | 184 | try: 185 | prices = self.client.get_coin_events_by_id(coin_id=parsy.symbol) 186 | except ValueError as e: 187 | print(f"{e}") 188 | return 189 | print_table(prices) 190 | 191 | def show_coin_twitter(self, args): 192 | parsy = self._show_coin_related(args) 193 | try: 194 | prices = self.client.get_coin_twitter_timeline(coin_id=parsy.symbol) 195 | except ValueError as e: 196 | print(f"{e}") 197 | return 198 | print_table(prices) 199 | 200 | def show_platforms(self, args): 201 | parser = MoonParser( 202 | prog="contract platforms", 203 | add_help=True, 204 | description="show all contract platforms", 205 | ) 206 | parsy, _ = parser.parse_known_args(args) 207 | try: 208 | platforms = self.client.get_all_contract_platforms() 209 | except ValueError as e: 210 | print(f"{e}") 211 | return 212 | print_table(platforms) 213 | 214 | def show_contracts(self, args): 215 | parser = MoonParser( 216 | prog="contract platforms", 217 | add_help=True, 218 | description="show all contract platforms", 219 | ) 220 | parser.add_argument( 221 | "-p", 222 | "--p", 223 | "--platform", 224 | dest="platform", 225 | help="Smart contract platform id. Default: eth-ethereum.", 226 | required=False, 227 | default="eth-ethereum", 228 | ) 229 | parsy, _ = parser.parse_known_args(args) 230 | try: 231 | platforms = self.client.get_contract_platform(parsy.platform) 232 | except ValueError as e: 233 | print(f"{e}") 234 | return 235 | print_table(platforms) 236 | 237 | def show_global_market(self, args): 238 | parser = MoonParser( 239 | prog="global market info", 240 | add_help=True, 241 | description="show global market info", 242 | ) 243 | parsy, _ = parser.parse_known_args(args) 244 | try: 245 | data = self.client.global_market() 246 | except ValueError as e: 247 | print(f"{e}") 248 | return 249 | print_table(data) 250 | 251 | def show_ohlc(self, args): 252 | parser = MoonParser( 253 | prog="coin ohlc", 254 | add_help=True, 255 | description="get open-high-low-close prices for given coin", 256 | ) 257 | parser.add_coin_argument(default="btc-bitcoin", help="coin id", dest="symbol") 258 | parser.add_limit_argument(default=365, help="Number of days", dest="limit") 259 | parsy, _ = parser.parse_known_args(args) 260 | try: 261 | prices = self.client.get_ohlc(coin_id=parsy.symbol) 262 | except ValueError as e: 263 | print(f"{e}") 264 | return 265 | print_table(prices.head(parsy.limit)) 266 | 267 | def search(self, args): 268 | parser = MoonParser( 269 | prog="search", 270 | add_help=True, 271 | description="search coinpaprika", 272 | ) 273 | parser.add_argument( 274 | "-q", 275 | "--q", 276 | "--query", 277 | required=True, 278 | type=str, 279 | help="Search query", 280 | dest="query", 281 | ) 282 | parsy, _ = parser.parse_known_args(args) 283 | 284 | try: 285 | search = self.client.search(q=parsy.query) 286 | except ValueError as e: 287 | print(f"{e}") 288 | return 289 | print_table(search) 290 | 291 | 292 | def main(): 293 | c = Controller() 294 | choices = c.base_commands + list(c.mapper.keys()) 295 | if sys.platform == "win32": 296 | os.system("") 297 | try: 298 | sys.stdin.reconfigure(encoding="utf-8") 299 | sys.stdout.reconfigure(encoding="utf-8") 300 | 301 | except Exception as e: 302 | print(e, "\n") 303 | 304 | parser = argparse.ArgumentParser(prog="paprika", add_help=False) 305 | parser.add_argument("cmd", choices=choices) 306 | 307 | print(LOGO) 308 | c.help() 309 | while True: 310 | 311 | an_input = input(f"{MOON}> (paprika) ") 312 | try: 313 | parsy, others = parser.parse_known_args(an_input.split()) 314 | cmd = parsy.cmd 315 | 316 | if cmd == "help": 317 | c.help() 318 | elif cmd in ["exit", "quit", "q"]: 319 | return True 320 | elif cmd == "r": 321 | return False 322 | 323 | view = c.mapper.get(cmd) 324 | if view is None: 325 | continue 326 | elif callable( 327 | view 328 | ): # If function takes params return func(args), else func() 329 | if len(signature(view).parameters) > 0: 330 | view(others) 331 | else: 332 | view() 333 | 334 | except ArgumentError: 335 | print("The command selected doesn't exist") 336 | print("\n") 337 | continue 338 | 339 | except SystemExit: 340 | time.sleep(0.1) 341 | print("") 342 | print("") 343 | continue 344 | 345 | 346 | if __name__ == "__main__": 347 | main() 348 | -------------------------------------------------------------------------------- /moonbag/cryptocompare/_client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from retry import retry 3 | 4 | ENDPOINTS = { 5 | "PRICE_MULTI_FULL": "/data/pricemultifull", 6 | "GENERATE_AVERAGE": "/data/generateAvg", 7 | "HISTO_DAY": "/data/histoday", 8 | "HISTO_HOUR": "/data/histohour", 9 | "HISTO_MINUTE": "/data/histominute", 10 | "TOP_LIST_24": "/data/top/totalvolfull", 11 | "TOP_LIST_24_FULL": "/data/top/totaltoptiervolfull", 12 | "TOP_BY_MARKET_CAP": "/data/top/mktcapfull", 13 | "TOP_EXCHANGES_FULL_DATA": "/data/top/exchanges/full", 14 | "TOP_LIST_PAIR_VOLUME": "/data/top/volumes", 15 | "TOP_LIST_OF_PAIRS": "/data/top/pairs", 16 | "EXCHANGE_TOP_SYMBOLS": "/data/exchange/top/volume", 17 | "ALL_COINS_LIST": "/data/all/coinlist", 18 | "LATEST_COIN_SOCIAL_STATS": "/data/social/coin/latest", 19 | "HISTO_DAY_SOCIAL_STATS": "/data/social/coin/histo/day", 20 | "DAILY_EXCHANGE_VOLUME": "/data/exchange/histoday", 21 | "HOURLY_EXCHANGE_VOLUME": "/data/exchange/histohour", 22 | "DAILY_SYMBOL_VOLUME": "/data/symbol/histoday", 23 | "HOURLY_SYMBOL_VOLUME": "/data/symbol/histohour", 24 | "LATEST_BLOCKCHAIN_DATA": "/data/blockchain/latest", 25 | "HISTO_BLOCKCHAIN_DATA": "/data/blockchain/histo/day", 26 | "TRADING_SIGNALS": "/data/tradingsignals/intotheblock/latest", 27 | "EXCHANGES_ORDER_BOOK": "/data/ob/exchanges", 28 | "ORDER_BOOK_L1_TOP": "/data/ob/l1/top", 29 | "ORDER_BOOK_L2_SNAPSHOT": "/data/v2/ob/l2/snapshot", 30 | "NEWS": "/data/v2/news/", 31 | "WALLETS": "/data/wallets/general", 32 | "GAMBLING": "/data/gambling/general", 33 | "MINING_CONTRACTS": "/data/mining/contracts/general", 34 | "MINING_COMPANIES": "/data/mining/companies/general", 35 | "RECOMMENDED": "/data/recommended/all", 36 | "FEEDS": "/data/news/feeds", 37 | "BLOCKCHAIN_COINS": "/data/blockchain/list", 38 | "EXCHANGES_PAIRS": "/data/v4/all/exchanges", 39 | "EXCHANGES_INFO": "/data/exchanges/general", 40 | } 41 | 42 | 43 | class CryptoCompareClient: 44 | BASE_URL = "https://min-api.cryptocompare.com" 45 | COMPARE_URL = "https://www.cryptocompare.com" 46 | 47 | def __init__(self, api_key): 48 | self.api_key = api_key 49 | 50 | @retry(tries=2, delay=3, max_delay=5) 51 | def _make_request(self, endpoint, payload=None, **kwargs): 52 | """You can use either endpoint key or endpoint value from dictionary ENDPOINTS 53 | All of request will be handled""" 54 | 55 | if endpoint in ENDPOINTS: 56 | endpoint_path = ENDPOINTS.get(endpoint) 57 | elif endpoint in list(ENDPOINTS.values()): 58 | endpoint_path = endpoint 59 | else: 60 | raise ValueError( 61 | f"Wrong endpoint\nPlease Use one from List {list(ENDPOINTS.keys())}" 62 | ) 63 | url = self.BASE_URL + endpoint_path 64 | if payload is None: 65 | payload = {} 66 | if kwargs: 67 | payload.update(kwargs) 68 | headers = {"authorization": "Apikey " + self.api_key} 69 | req = requests.get(url, params=payload, headers=headers) 70 | return req.json() 71 | 72 | def _get_price(self, symbol="BTC", currency="USD", **kwargs): 73 | """Full data""" 74 | endpoint = ENDPOINTS["PRICE_MULTI_FULL"] 75 | payload = { 76 | "fsyms": symbol, 77 | "tsyms": currency, 78 | "relaxedValidation": "false", 79 | } 80 | return self._make_request(endpoint, payload, **kwargs) 81 | 82 | def _get_top_list_by_market_cap(self, currency="USD", limit=100, **kwargs): 83 | endpoint = ENDPOINTS["TOP_BY_MARKET_CAP"] 84 | payload = {"tsym": currency, "limit": limit} 85 | return self._make_request(endpoint, payload, **kwargs) 86 | 87 | def _get_top_exchanges(self, symbol="BTC", currency="USD", limit=100, **kwargs): 88 | endpoint = ENDPOINTS["TOP_EXCHANGES_FULL_DATA"] 89 | payload = { 90 | "fsym": symbol, 91 | "tsym": currency, 92 | "limit": limit, 93 | } 94 | return self._make_request(endpoint, payload, **kwargs) 95 | 96 | def _get_exchanges_top_symbols_by_volume( 97 | self, exchange="Binance", limit=100, **kwargs 98 | ): 99 | "/data/exchange/top/volume?e=Binance&direction=TO" 100 | "e=Kraken" 101 | endpoint = ENDPOINTS["EXCHANGE_TOP_SYMBOLS"] 102 | payload = {"e": exchange.capitalize(), "limit": limit, "direction": "FROM"} 103 | return self._make_request(endpoint, payload, **kwargs) 104 | 105 | def _get_top_list_by_pair_volume(self, currency="USD", limit=100, **kwargs): 106 | endpoint = ENDPOINTS["TOP_LIST_PAIR_VOLUME"] 107 | payload = { 108 | "tsym": currency, 109 | "limit": limit, 110 | } 111 | return self._make_request(endpoint, payload, **kwargs) 112 | 113 | def _get_top_of_trading_pairs(self, symbol="ETH", limit=50, **kwargs): 114 | endpoint = ENDPOINTS["TOP_LIST_OF_PAIRS"] 115 | payload = { 116 | "fsym": symbol, 117 | "limit": limit, 118 | } 119 | return self._make_request(endpoint, payload, **kwargs) 120 | 121 | # TODO --> Add mappings that use can use either coinId, symbol, or name for requests 122 | def _get_latest_social_coin_stats(self, coin_id=7605, **kwargs): 123 | endpoint = ENDPOINTS["LATEST_COIN_SOCIAL_STATS"] 124 | payload = {"coinId": int(coin_id)} 125 | return self._make_request(endpoint, payload, **kwargs) 126 | 127 | def _get_historical_social_stats( 128 | self, coin_id=7605, limit=2000, aggregate=1, **kwargs 129 | ): 130 | endpoint = ENDPOINTS["HISTO_DAY_SOCIAL_STATS"] 131 | payload = { 132 | "coinId": int(coin_id), 133 | "limit": limit, 134 | "aggregate": aggregate, 135 | } 136 | return self._make_request(endpoint, payload, **kwargs) 137 | 138 | def _get_latest_news(self, lang="EN", sort_order="latest", **kwargs): 139 | endpoint = ENDPOINTS["NEWS"] 140 | payload = {"lang": lang, "sortOrder": sort_order} 141 | return self._make_request(endpoint, payload, **kwargs) 142 | 143 | def _get_blockchain_available_coins_list(self): 144 | endpoint = ENDPOINTS["BLOCKCHAIN_COINS"] 145 | return self._make_request(endpoint=endpoint) 146 | 147 | def _get_all_coins_list(self, summary="true", **kwargs): 148 | endpoint = ENDPOINTS["ALL_COINS_LIST"] 149 | if isinstance(summary, bool): 150 | summary = str(summary).lower() 151 | 152 | payload = {"summary": summary} 153 | return self._make_request(endpoint, payload, **kwargs) 154 | 155 | def _get_historical_day_prices( 156 | self, symbol="BTC", currency="USD", limit=2000, **kwargs 157 | ): 158 | endpoint = ENDPOINTS["HISTO_DAY"] 159 | payload = { 160 | "fsym": symbol, 161 | "tsym": currency, 162 | "limit": limit, 163 | } 164 | return self._make_request(endpoint, payload, **kwargs) 165 | 166 | def _get_historical_hour_prices( 167 | self, symbol="BTC", currency="USD", limit=2000, **kwargs 168 | ): 169 | endpoint = ENDPOINTS["HISTO_HOUR"] 170 | payload = { 171 | "fsym": symbol, 172 | "tsym": currency, 173 | "limit": limit, 174 | } 175 | return self._make_request(endpoint, payload, **kwargs) 176 | 177 | def _get_historical_minutes_prices( 178 | self, symbol="BTC", currency="USD", limit=2000, **kwargs 179 | ): 180 | endpoint = ENDPOINTS["HISTO_MINUTE"] 181 | payload = { 182 | "fsym": symbol, 183 | "tsym": currency, 184 | "limit": limit, 185 | } 186 | return self._make_request(endpoint, payload, **kwargs) 187 | 188 | def _get_daily_exchange_volume( 189 | self, currency="USD", exchange="CCCAGG", limit=365, **kwargs 190 | ): 191 | """The CCCAGG is calculated for each crypto coin in each currency it is trading in (example: CCCAGG BTC-USD) 192 | See more: https://www.cryptocompare.com/media/12318004/cccagg.pdf 193 | """ 194 | endpoint = ENDPOINTS["DAILY_EXCHANGE_VOLUME"] 195 | payload = { 196 | "tsym": currency, 197 | "e": exchange, 198 | "limit": limit, 199 | } 200 | return self._make_request(endpoint, payload, **kwargs) 201 | 202 | def _get_hourly_exchange_volume( 203 | self, currency="USD", exchange="CCCAGG", limit=60 * 24, **kwargs 204 | ): 205 | """The CCCAGG is calculated for each crypto coin in each currency it is trading in (example: CCCAGG BTC-USD) 206 | See more: https://www.cryptocompare.com/media/12318004/cccagg.pdf 207 | """ 208 | endpoint = ENDPOINTS["HOURLY_EXCHANGE_VOLUME"] 209 | payload = { 210 | "tsym": currency, 211 | "e": exchange, 212 | "limit": limit, 213 | } 214 | return self._make_request(endpoint, payload, **kwargs) 215 | 216 | def _get_daily_symbol_volume( 217 | self, symbol="BTC", currency="USD", limit=365, **kwargs 218 | ): 219 | endpoint = ENDPOINTS["DAILY_SYMBOL_VOLUME"] 220 | payload = { 221 | "fsym": symbol, 222 | "tsym": currency, 223 | "limit": limit, 224 | } 225 | return self._make_request(endpoint, payload, **kwargs) 226 | 227 | def _get_hourly_symbol_volume( 228 | self, symbol="BTC", currency="USD", limit=60 * 24, **kwargs 229 | ): 230 | endpoint = ENDPOINTS["HOURLY_SYMBOL_VOLUME"] 231 | payload = { 232 | "fsym": symbol, 233 | "tsym": currency, 234 | "limit": limit, 235 | } 236 | return self._make_request(endpoint, payload, **kwargs) 237 | 238 | def _get_latest_blockchain_data(self, symbol="BTC", **kwargs): 239 | endpoint = ENDPOINTS["LATEST_BLOCKCHAIN_DATA"] 240 | payload = { 241 | "fsym": symbol, 242 | } 243 | return self._make_request(endpoint, payload, **kwargs) 244 | 245 | def _get_historical_blockchain_data(self, symbol="BTC", limit=365, **kwargs): 246 | endpoint = ENDPOINTS["HISTO_BLOCKCHAIN_DATA"] 247 | payload = { 248 | "fsym": symbol, 249 | "limit": limit, 250 | } 251 | return self._make_request(endpoint, payload, **kwargs) 252 | 253 | def _get_latest_trading_signals(self, symbol="BTC", **kwargs): 254 | endpoint = ENDPOINTS["TRADING_SIGNALS"] 255 | payload = { 256 | "fsym": symbol, 257 | } 258 | return self._make_request(endpoint, payload, **kwargs) 259 | 260 | def _get_order_books_exchanges(self, **kwargs): 261 | endpoint = ENDPOINTS["EXCHANGES_ORDER_BOOK"] 262 | return self._make_request(endpoint, {}, **kwargs) 263 | 264 | def _get_order_book_top( 265 | self, symbol="ETH", to_symbol="BTC", exchange="binance", **kwargs 266 | ): 267 | """Returns latest order book Level 1 bid/ask values 268 | for the requested exchange and pairs in both raw and display formats""" 269 | endpoint = ENDPOINTS["ORDER_BOOK_L1_TOP"] 270 | payload = { 271 | "fsyms": symbol, 272 | "tsyms": to_symbol, 273 | "e": exchange, 274 | } 275 | return self._make_request(endpoint, payload, **kwargs) 276 | 277 | def _get_order_book_snapshot( 278 | self, symbol="ETH", to_symbol="BTC", exchange="binance", **kwargs 279 | ): 280 | """Returns latest order book Level 2 data snapshot for the requested exchang""" 281 | endpoint = ENDPOINTS["ORDER_BOOK_L2_SNAPSHOT"] 282 | payload = { 283 | "fsyms": symbol, 284 | "tsyms": to_symbol, 285 | "e": exchange, 286 | } 287 | return self._make_request(endpoint, payload, **kwargs) 288 | 289 | def _get_all_exchanges_and_trading_pairs( 290 | self, symbol=None, exchange=None, top_tier="false", **kwargs 291 | ): 292 | endpoint = ENDPOINTS["EXCHANGES_PAIRS"] 293 | payload = { 294 | "fsym": symbol, 295 | "e": exchange, 296 | "topTier": top_tier, 297 | } 298 | return self._make_request(endpoint, payload, **kwargs) 299 | 300 | def _get_all_exchanges_info(self, symbol="BTC", **kwargs): 301 | endpoint = ENDPOINTS["EXCHANGES_INFO"] 302 | payload = { 303 | "tsym": symbol, 304 | } 305 | return self._make_request(endpoint, payload, **kwargs) 306 | 307 | def _get_all_wallet_info(self, **kwargs): 308 | endpoint = ENDPOINTS["WALLETS"] 309 | payload = {} 310 | return self._make_request(endpoint, payload, **kwargs) 311 | 312 | def _get_all_gambling_info(self, **kwargs): 313 | endpoint = ENDPOINTS["GAMBLING"] 314 | payload = {} 315 | return self._make_request(endpoint, payload, **kwargs) 316 | 317 | def _get_recommendations(self, symbol="BTC", **kwargs): 318 | """Returns general info about our recommended entities. 319 | * wallets, 320 | * gambling 321 | * mining companies 322 | * exchanges 323 | """ 324 | endpoint = ENDPOINTS["RECOMMENDED"] 325 | payload = {"tsym": symbol} 326 | return self._make_request(endpoint, payload, **kwargs) 327 | -------------------------------------------------------------------------------- /moonbag/discover/menu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import os 4 | import argparse 5 | import logging 6 | import time 7 | 8 | from moonbag.common import LOGO, MOON, print_table 9 | from argparse import ArgumentError 10 | from inspect import signature 11 | from moonbag.common.utils import MoonParser 12 | from moonbag.discover.defi import graph, llama, pulse 13 | from moonbag.discover.reddit_client import reddit 14 | from moonbag.discover.others import wales 15 | from moonbag.discover.others import fng, funding, fourchan, cryptopanic 16 | 17 | logger = logging.getLogger("discover-menu") 18 | 19 | 20 | class Controller: 21 | def __init__(self): 22 | self.reddit = reddit.Reddit() 23 | self.graph = graph.GraphClient() 24 | self.parser = argparse.ArgumentParser(prog="discover", add_help=False) 25 | self.parser.add_argument("cmd") 26 | self.base_commands = ["help", "exit", "quit", "r", "q"] 27 | self.mapper = { 28 | "top_subs": self.show_popular_submissions, 29 | "search_reddit": self.search_reddit, 30 | "search_subs": self.search_subs, 31 | "dpi": self.show_defi_pulse_index, 32 | "defi": self.show_llama_protocols, 33 | "fng": self.show_fear_greed, 34 | "news": self.show_cryptopanic, 35 | "fundings": self.show_fundings, 36 | "4chan": self.show_4chan, 37 | "wales": self.show_wales, 38 | "uni_pairs": self.show_lastly_added_tokens, 39 | "dex_trades": self.show_dex_trades, 40 | "compound": self.show_compound_markets, 41 | "uni_tokens": self.show_uni_tokens, 42 | "uni_swaps": self.show_last_swaps_uni, 43 | } 44 | 45 | @staticmethod 46 | def help(): 47 | print("Main commands:") 48 | print(" help show help") 49 | print(" r return to previous menu") 50 | print(" quit quit program") 51 | print("") 52 | print("Discovery mode ") 53 | print( 54 | " top_subs find top submissions on most popular crypto subreddits [Reddit]" 55 | ) 56 | print(" search_subs search for new submissions in given subreddit [Reddit]") 57 | print(" search_reddit search on reddit with you own query [Reddit]") 58 | print(" dpi show defi pulse index [Reddit]") 59 | print(" defi show DeFi protocols stats [LLama]") 60 | print( 61 | " fng show fear and greed index for last n days [Fear and greed]" 62 | ) 63 | print(" news show last crypto news [Cryptopanic]") 64 | print(" fundings show crypto funding rates [Defirate]") 65 | print(" 4chan show last 4chan submissions [4chan]") 66 | print(" wales show wales transactions [Whale-Alert]") 67 | print(" uni_pairs show recently added pairs on UniSwap [TheGraph]") 68 | print(" uni_tokens show uni tokens [TheGraph]") 69 | print(" uni_swaps show latest swaps on UniSwap [TheGraph]") 70 | print(" dex_trades show stats about DeFi dex trades [TheGraph]") 71 | print(" compound show compound markets [TheGraph]") 72 | print(" ") 73 | return 74 | 75 | def show_top_submissions(self, args): 76 | parser = MoonParser( 77 | prog="reddit", 78 | add_help=True, 79 | description="get reddit information", 80 | ) 81 | parser.add_key_argument( 82 | default="top", 83 | choices=["top", "controversial", "hot"], 84 | help="Number of coins", 85 | dest="key", 86 | ) 87 | parsy, _ = parser.parse_known_args(args) 88 | 89 | if self.reddit is None: 90 | print("You need to provide Reddit Credentials to use this API") 91 | return 92 | 93 | try: 94 | top_subs = self.reddit.discover_top_submissions(parsy.key) 95 | except ValueError as e: 96 | print(f"{e}") 97 | return 98 | print_table(top_subs) 99 | 100 | def show_popular_submissions(self, args): 101 | if self.reddit.client is None: 102 | print("You need to provide Reddit Credentials to use this API") 103 | return 104 | parser = MoonParser( 105 | prog="reddit sumissions", 106 | add_help=True, 107 | description="get reddit information", 108 | ) 109 | 110 | parsy, _ = parser.parse_known_args(args) 111 | try: 112 | top_subs = self.reddit.get_popular_submissions() 113 | except ValueError as e: 114 | print(f"{e}") 115 | return 116 | print_table(top_subs) 117 | 118 | def search_reddit(self, args): 119 | parser = MoonParser( 120 | prog="reddit search", 121 | add_help=True, 122 | description="search on reddit", 123 | ) 124 | parser.add_argument( 125 | "-q", 126 | "--q", 127 | "--query", 128 | help="search query", 129 | dest="query", 130 | required=True, 131 | type=str, 132 | ) 133 | parser.add_argument( 134 | "-t", 135 | "--type", 136 | help="type of data: submission or comment", 137 | dest="type", 138 | required=False, 139 | type=str, 140 | choices=["submission", "comment"], 141 | default="submission", 142 | ) 143 | 144 | parsy, _ = parser.parse_known_args(args) 145 | try: 146 | search_data = self.reddit.search(query=parsy.query, data_type=parsy.type) 147 | except ValueError as e: 148 | print(f"{e}") 149 | return 150 | print_table(search_data) 151 | 152 | def search_subs(self, args): 153 | if self.reddit.client is None: 154 | print("You need to provide Reddit Credentials to use this API") 155 | return 156 | parser = MoonParser( 157 | prog="reddit search", 158 | add_help=True, 159 | description="search on reddit", 160 | ) 161 | parser.add_argument( 162 | "-s", 163 | "--s", 164 | "--subreddit", 165 | help="subreddit name example: CryptoMoonShots ", 166 | dest="subreddit", 167 | required=True, 168 | type=str, 169 | ) 170 | 171 | parsy, _ = parser.parse_known_args(args) 172 | try: 173 | search_data = self.reddit.get_submissions_for_subreddits( 174 | subreddits=parsy.subreddit 175 | ) 176 | except ValueError as e: 177 | print(f"{e}") 178 | return 179 | print_table(search_data) 180 | 181 | @staticmethod 182 | def show_defi_pulse_index(args): 183 | parser = MoonParser( 184 | prog="dpi", 185 | add_help=True, 186 | description="get defi pulse data", 187 | ) 188 | 189 | parsy, _ = parser.parse_known_args(args) 190 | try: 191 | dpi = pulse.get_dpi() 192 | except ValueError as e: 193 | print(f"{e}") 194 | return 195 | print_table(dpi, floatfmt="0.0f") 196 | 197 | @staticmethod 198 | def show_llama_protocols(args): 199 | lama = llama.LLama() 200 | parser = MoonParser( 201 | prog="defi protocols [llama]", 202 | add_help=True, 203 | description="get defi protocols data from llama", 204 | ) 205 | 206 | parsy, _ = parser.parse_known_args(args) 207 | try: 208 | dpi = lama.get_protocols() 209 | except ValueError as e: 210 | print(f"{e}") 211 | return 212 | print_table(dpi, floatfmt="0.4f") 213 | 214 | @staticmethod 215 | def show_fear_greed(args): 216 | parser = MoonParser( 217 | prog="feer and greed index", 218 | add_help=True, 219 | description="feer and greed index", 220 | ) 221 | parser.add_limit_argument( 222 | help="last N days", dest="limit", default=30, required=False 223 | ) 224 | parsy, _ = parser.parse_known_args(args) 225 | try: 226 | dpi = fng.get_fng(parsy.limit) 227 | except ValueError as e: 228 | print(f"{e}") 229 | return 230 | print_table(dpi, floatfmt="0.0f") 231 | 232 | @staticmethod 233 | def show_cryptopanic(args): 234 | parser = MoonParser( 235 | prog="crypto panic news & media", 236 | add_help=True, 237 | description="crypto panic news & media", 238 | ) 239 | cpanic = cryptopanic.CryptoPanic() 240 | 241 | parser.add_key_argument( 242 | dest="key", choices=["news", "media"], required=False, default="news" 243 | ) 244 | parsy, _ = parser.parse_known_args(args) 245 | try: 246 | news = cpanic.get_posts(kind=parsy.key) 247 | except ValueError as e: 248 | print(f"{e}") 249 | return 250 | print_table(news, floatfmt="0.0f") 251 | 252 | @staticmethod 253 | def show_fundings(args): 254 | parser = MoonParser( 255 | prog="crypto fundings rate", 256 | add_help=True, 257 | description="crypto fundings rate", 258 | ) 259 | parser.add_key_argument( 260 | dest="key", choices=["current", "avg"], required=False, default="current" 261 | ) 262 | 263 | parsy, _ = parser.parse_known_args(args) 264 | try: 265 | if parsy.key == "avg": 266 | key = False 267 | else: 268 | key = True 269 | fundings = funding.get_funding_rates(current=key) 270 | except ValueError as e: 271 | print(f"{e}") 272 | return 273 | print_table(fundings, floatfmt="0.0f") 274 | 275 | @staticmethod 276 | def show_4chan(args): 277 | parser = MoonParser( 278 | prog="last 4chan posts", 279 | add_help=True, 280 | description="last 4chan posts", 281 | ) 282 | 283 | parsy, _ = parser.parse_known_args(args) 284 | try: 285 | chans = fourchan.get_last_4chans() 286 | except ValueError as e: 287 | print(f"{e}") 288 | return 289 | print_table(chans, floatfmt="0.0f") 290 | 291 | @staticmethod 292 | def show_wales(args): 293 | parser = MoonParser( 294 | prog="wales", 295 | add_help=True, 296 | description="last wales txs", 297 | ) 298 | parsy, _ = parser.parse_known_args(args) 299 | try: 300 | chans = wales.get_wales_stats() 301 | except ValueError as e: 302 | print(f"{e}") 303 | return 304 | print_table(chans, floatfmt="0.0f") 305 | 306 | def show_uni_tokens(self, args): 307 | parser = MoonParser( 308 | prog="unitokens", 309 | add_help=True, 310 | description="show uni tokens", 311 | ) 312 | parser.add_argument( 313 | "-s", "--skip", help="skip records", default=0, required=False, dest="skip" 314 | ) 315 | parsy, _ = parser.parse_known_args(args) 316 | try: 317 | tokens = self.graph.get_uni_tokens(skip=parsy.skip) 318 | except ValueError as e: 319 | print(f"{e}") 320 | return 321 | print_table(tokens, floatfmt="0.2f") 322 | 323 | def show_dex_trades(self, args): 324 | parser = MoonParser( 325 | prog="dextrades", 326 | add_help=True, 327 | description="show dex trades by protocol", 328 | ) 329 | parser.add_key_argument( 330 | help="Dex trades by protocol or by protocol/month", 331 | choices=["protocol", "moth"], 332 | default="protocol", 333 | required=False, 334 | ) 335 | parsy, _ = parser.parse_known_args(args) 336 | try: 337 | if parsy.key == "month": 338 | dexs = self.graph.get_dex_trades_monthly() 339 | else: 340 | dexs = self.graph.get_dex_trades_by_protocol() 341 | except ValueError as e: 342 | print(f"{e}") 343 | return 344 | print_table(dexs, floatfmt="0.2f") 345 | 346 | def show_lastly_added_tokens(self, args): 347 | parser = MoonParser( 348 | prog="lastly added pairs on uniswap", 349 | add_help=True, 350 | description="lastly added pairs on uniswap", 351 | ) 352 | 353 | parser.add_argument( 354 | "-d", 355 | "--d", 356 | "--days", 357 | help="last n of days", 358 | dest="day", 359 | default=14, 360 | required=False, 361 | type=int, 362 | ) 363 | parser.add_argument( 364 | "-v", 365 | "--v", 366 | "--volume", 367 | help="min volume", 368 | dest="volume", 369 | default=1000, 370 | required=False, 371 | type=int, 372 | ) 373 | parser.add_argument( 374 | "-l", 375 | "--l", 376 | "--liquid", 377 | help="min liquidity", 378 | dest="liquid", 379 | default=0, 380 | required=False, 381 | type=int, 382 | ) 383 | parser.add_argument( 384 | "-t", 385 | "--t", 386 | "--txs", 387 | help="min transactions", 388 | dest="txs", 389 | default=1000, 390 | required=False, 391 | type=int, 392 | ) 393 | 394 | parsy, _ = parser.parse_known_args(args) 395 | 396 | try: 397 | pairs = self.graph.get_uniswap_pool_lastly_added( 398 | last_days=parsy.day, 399 | min_volume=parsy.volume, 400 | min_liquidity=parsy.liquid, 401 | min_tx=parsy.txs, 402 | ) 403 | except ValueError as e: 404 | print(f"{e}") 405 | return 406 | print_table(pairs, floatfmt="0.2f") 407 | 408 | def show_last_swaps_uni(self, args): 409 | parser = MoonParser( 410 | prog="last swaps uniswap", 411 | add_help=True, 412 | description="last swaps uniswap", 413 | ) 414 | parsy, _ = parser.parse_known_args(args) 415 | try: 416 | comp = self.graph.get_last_swaps_uni() 417 | except ValueError as e: 418 | print(f"{e}") 419 | return 420 | print_table(comp, floatfmt="0.2f") 421 | 422 | def show_compound_markets(self, args): 423 | parser = MoonParser( 424 | prog="compound", 425 | add_help=True, 426 | description="show compound markets", 427 | ) 428 | parsy, _ = parser.parse_known_args(args) 429 | try: 430 | comp = self.graph.get_compound_markets() 431 | except ValueError as e: 432 | print(f"{e}") 433 | return 434 | print_table(comp, floatfmt="0.2f") 435 | 436 | 437 | def main(): 438 | c = Controller() 439 | choices = c.base_commands + list(c.mapper.keys()) 440 | if sys.platform == "win32": 441 | os.system("") 442 | 443 | parser = argparse.ArgumentParser(prog="discover mode", add_help=False) 444 | parser.add_argument("cmd", choices=choices) 445 | 446 | print(LOGO) 447 | c.help() 448 | while True: 449 | 450 | an_input = input(f"{MOON}> (discover) ") 451 | try: 452 | parsy, others = parser.parse_known_args(an_input.split()) 453 | cmd = parsy.cmd 454 | 455 | if cmd == "help": 456 | c.help() 457 | elif cmd in ["exit", "quit", "q"]: 458 | return True 459 | elif cmd == "r": 460 | return False 461 | 462 | view = c.mapper.get(cmd) 463 | if view is None: 464 | continue 465 | elif callable( 466 | view 467 | ): # If function takes params return func(args), else func() 468 | if len(signature(view).parameters) > 0: 469 | view(others) 470 | else: 471 | view() 472 | else: 473 | print("Command not found") 474 | 475 | except ArgumentError: 476 | print("The command selected doesn't exist") 477 | print("\n") 478 | continue 479 | 480 | except SystemExit: 481 | time.sleep(0.1) 482 | print("") 483 | print("") 484 | continue 485 | 486 | 487 | if __name__ == "__main__": 488 | main() 489 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-whitelist= 7 | 8 | # Specify a score threshold to be exceeded before program exits with error. 9 | fail-under=10 10 | 11 | # Add files or directories to the blacklist. They should be base names, not 12 | # paths. 13 | ignore=CVS 14 | 15 | # Add files or directories matching the regex patterns to the blacklist. The 16 | # regex matches against base names, not paths. 17 | ignore-patterns= 18 | 19 | # Python code to execute, usually for sys.path manipulation such as 20 | # pygtk.require(). 21 | #init-hook= 22 | 23 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 24 | # number of processors available to use. 25 | jobs=1 26 | 27 | # Control the amount of potential inferred values when inferring a single 28 | # object. This can help the performance when dealing with large functions or 29 | # complex, nested conditions. 30 | limit-inference-results=100 31 | 32 | # List of plugins (as comma separated values of python module names) to load, 33 | # usually to register additional checkers. 34 | load-plugins= 35 | 36 | # Pickle collected data for later comparisons. 37 | persistent=yes 38 | 39 | # When enabled, pylint would attempt to guess common misconfiguration and emit 40 | # user-friendly hints instead of false-positive error messages. 41 | suggestion-mode=yes 42 | 43 | # Allow loading of arbitrary C extensions. Extensions are imported into the 44 | # active Python interpreter and may run arbitrary code. 45 | unsafe-load-any-extension=no 46 | 47 | 48 | [MESSAGES CONTROL] 49 | 50 | # Only show warnings with the listed confidence levels. Leave empty to show 51 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 52 | confidence= 53 | 54 | # Disable the message, report, category or checker with the given id(s). You 55 | # can either give multiple identifiers separated by comma (,) or put this 56 | # option multiple times (only on the command line, not in the configuration 57 | # file where it should appear only once). You can also use "--disable=all" to 58 | # disable everything first and then reenable specific checks. For example, if 59 | # you want to run only the similarities checker, you can use "--disable=all 60 | # --enable=similarities". If you want to run only the classes checker, but have 61 | # no Warning level messages displayed, use "--disable=all --enable=classes 62 | # --disable=W". 63 | disable=print-statement, 64 | invalid-name, 65 | locally-disabled, 66 | bad-continuation, 67 | parameter-unpacking, 68 | unpacking-in-except, 69 | old-raise-syntax, 70 | backtick, 71 | long-suffix, 72 | old-ne-operator, 73 | old-octal-literal, 74 | import-star-module-level, 75 | non-ascii-bytes-literal, 76 | raw-checker-failed, 77 | bad-inline-option, 78 | locally-disabled, 79 | file-ignored, 80 | suppressed-message, 81 | useless-suppression, 82 | deprecated-pragma, 83 | use-symbolic-message-instead, 84 | apply-builtin, 85 | basestring-builtin, 86 | buffer-builtin, 87 | cmp-builtin, 88 | coerce-builtin, 89 | execfile-builtin, 90 | file-builtin, 91 | long-builtin, 92 | raw_input-builtin, 93 | reduce-builtin, 94 | standarderror-builtin, 95 | unicode-builtin, 96 | xrange-builtin, 97 | coerce-method, 98 | delslice-method, 99 | getslice-method, 100 | setslice-method, 101 | no-absolute-import, 102 | old-division, 103 | dict-iter-method, 104 | dict-view-method, 105 | next-method-called, 106 | metaclass-assignment, 107 | indexing-exception, 108 | raising-string, 109 | reload-builtin, 110 | oct-method, 111 | hex-method, 112 | nonzero-method, 113 | cmp-method, 114 | input-builtin, 115 | round-builtin, 116 | intern-builtin, 117 | unichr-builtin, 118 | map-builtin-not-iterating, 119 | zip-builtin-not-iterating, 120 | range-builtin-not-iterating, 121 | filter-builtin-not-iterating, 122 | using-cmp-argument, 123 | eq-without-hash, 124 | div-method, 125 | idiv-method, 126 | rdiv-method, 127 | exception-message-attribute, 128 | invalid-str-codec, 129 | sys-max-int, 130 | bad-python3-import, 131 | deprecated-string-function, 132 | deprecated-str-translate-call, 133 | deprecated-itertools-function, 134 | deprecated-types-field, 135 | next-method-defined, 136 | dict-items-not-iterating, 137 | dict-keys-not-iterating, 138 | dict-values-not-iterating, 139 | deprecated-operator-function, 140 | deprecated-urllib-function, 141 | xreadlines-attribute, 142 | deprecated-sys-function, 143 | exception-escape, 144 | comprehension-escape 145 | 146 | # Enable the message, report, category or checker with the given id(s). You can 147 | # either give multiple identifier separated by comma (,) or put this option 148 | # multiple time (only on the command line, not in the configuration file where 149 | # it should appear only once). See also the "--disable" option for examples. 150 | enable=c-extension-no-member 151 | 152 | 153 | [REPORTS] 154 | 155 | # Python expression which should return a score less than or equal to 10. You 156 | # have access to the variables 'error', 'warning', 'refactor', and 'convention' 157 | # which contain the number of messages in each category, as well as 'statement' 158 | # which is the total number of statements analyzed. This score is used by the 159 | # global evaluation report (RP0004). 160 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 161 | 162 | # Template used to display messages. This is a python new-style format string 163 | # used to format the message information. See doc for all details. 164 | #msg-template= 165 | 166 | # Set the output format. Available formats are text, parseable, colorized, json 167 | # and msvs (visual studio). You can also give a reporter class, e.g. 168 | # mypackage.mymodule.MyReporterClass. 169 | output-format=text 170 | 171 | # Tells whether to display a full report or only the messages. 172 | reports=no 173 | 174 | # Activate the evaluation score. 175 | score=yes 176 | 177 | 178 | [REFACTORING] 179 | 180 | # Maximum number of nested blocks for function / method body 181 | max-nested-blocks=5 182 | 183 | # Complete name of functions that never returns. When checking for 184 | # inconsistent-return-statements if a never returning function is called then 185 | # it will be considered as an explicit return statement and no message will be 186 | # printed. 187 | never-returning-functions=sys.exit 188 | 189 | 190 | [BASIC] 191 | 192 | # Naming style matching correct argument names. 193 | argument-naming-style=snake_case 194 | 195 | # Regular expression matching correct argument names. Overrides argument- 196 | # naming-style. 197 | #argument-rgx= 198 | 199 | # Naming style matching correct attribute names. 200 | attr-naming-style=snake_case 201 | 202 | # Regular expression matching correct attribute names. Overrides attr-naming- 203 | # style. 204 | #attr-rgx= 205 | 206 | # Bad variable names which should always be refused, separated by a comma. 207 | bad-names=foo, 208 | bar, 209 | baz, 210 | toto, 211 | tutu, 212 | tata 213 | 214 | # Bad variable names regexes, separated by a comma. If names match any regex, 215 | # they will always be refused 216 | bad-names-rgxs= 217 | 218 | # Naming style matching correct class attribute names. 219 | class-attribute-naming-style=any 220 | 221 | # Regular expression matching correct class attribute names. Overrides class- 222 | # attribute-naming-style. 223 | #class-attribute-rgx= 224 | 225 | # Naming style matching correct class names. 226 | class-naming-style=PascalCase 227 | 228 | # Regular expression matching correct class names. Overrides class-naming- 229 | # style. 230 | #class-rgx= 231 | 232 | # Naming style matching correct constant names. 233 | const-naming-style=UPPER_CASE 234 | 235 | # Regular expression matching correct constant names. Overrides const-naming- 236 | # style. 237 | #const-rgx= 238 | 239 | # Minimum line length for functions/classes that require docstrings, shorter 240 | # ones are exempt. 241 | docstring-min-length=-1 242 | 243 | # Naming style matching correct function names. 244 | function-naming-style=snake_case 245 | 246 | # Regular expression matching correct function names. Overrides function- 247 | # naming-style. 248 | #function-rgx= 249 | 250 | # Good variable names which should always be accepted, separated by a comma. 251 | good-names=i, 252 | j, 253 | k, 254 | ex, 255 | Run, 256 | _ 257 | 258 | # Good variable names regexes, separated by a comma. If names match any regex, 259 | # they will always be accepted 260 | good-names-rgxs= 261 | 262 | # Include a hint for the correct naming format with invalid-name. 263 | include-naming-hint=no 264 | 265 | # Naming style matching correct inline iteration names. 266 | inlinevar-naming-style=any 267 | 268 | # Regular expression matching correct inline iteration names. Overrides 269 | # inlinevar-naming-style. 270 | #inlinevar-rgx= 271 | 272 | # Naming style matching correct method names. 273 | method-naming-style=snake_case 274 | 275 | # Regular expression matching correct method names. Overrides method-naming- 276 | # style. 277 | #method-rgx= 278 | 279 | # Naming style matching correct module names. 280 | module-naming-style=snake_case 281 | 282 | # Regular expression matching correct module names. Overrides module-naming- 283 | # style. 284 | #module-rgx= 285 | 286 | # Colon-delimited sets of names that determine each other's naming style when 287 | # the name regexes allow several styles. 288 | name-group= 289 | 290 | # Regular expression which should only match function or class names that do 291 | # not require a docstring. 292 | no-docstring-rgx=^_ 293 | 294 | # List of decorators that produce properties, such as abc.abstractproperty. Add 295 | # to this list to register other decorators that produce valid properties. 296 | # These decorators are taken in consideration only for invalid-name. 297 | property-classes=abc.abstractproperty 298 | 299 | # Naming style matching correct variable names. 300 | variable-naming-style=snake_case 301 | 302 | # Regular expression matching correct variable names. Overrides variable- 303 | # naming-style. 304 | #variable-rgx= 305 | 306 | 307 | [FORMAT] 308 | 309 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 310 | expected-line-ending-format= 311 | 312 | # Regexp for a line that is allowed to be longer than the limit. 313 | ignore-long-lines=^\s*(# )??$ 314 | 315 | # Number of spaces of indent required inside a hanging or continued line. 316 | indent-after-paren=4 317 | 318 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 319 | # tab). 320 | indent-string=' ' 321 | 322 | # Maximum number of characters on a single line. 323 | max-line-length=100 324 | 325 | # Maximum number of lines in a module. 326 | max-module-lines=1000 327 | 328 | # List of optional constructs for which whitespace checking is disabled. `dict- 329 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 330 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 331 | # `empty-line` allows space-only lines. 332 | no-space-check=trailing-comma, 333 | dict-separator 334 | 335 | # Allow the body of a class to be on the same line as the declaration if body 336 | # contains single statement. 337 | single-line-class-stmt=no 338 | 339 | # Allow the body of an if to be on the same line as the test if there is no 340 | # else. 341 | single-line-if-stmt=no 342 | 343 | 344 | [LOGGING] 345 | 346 | # The type of string formatting that logging methods do. `old` means using % 347 | # formatting, `new` is for `{}` formatting. 348 | logging-format-style=old 349 | 350 | # Logging modules to check that the string format arguments are in logging 351 | # function parameter format. 352 | logging-modules=logging 353 | 354 | 355 | [MISCELLANEOUS] 356 | 357 | # List of note tags to take in consideration, separated by a comma. 358 | notes=FIXME, 359 | XXX, 360 | TODO 361 | 362 | # Regular expression of note tags to take in consideration. 363 | #notes-rgx= 364 | 365 | 366 | [SIMILARITIES] 367 | 368 | # Ignore comments when computing similarities. 369 | ignore-comments=yes 370 | 371 | # Ignore docstrings when computing similarities. 372 | ignore-docstrings=yes 373 | 374 | # Ignore imports when computing similarities. 375 | ignore-imports=no 376 | 377 | # Minimum lines number of a similarity. 378 | min-similarity-lines=4 379 | 380 | 381 | [SPELLING] 382 | 383 | # Limits count of emitted suggestions for spelling mistakes. 384 | max-spelling-suggestions=4 385 | 386 | # Spelling dictionary name. Available dictionaries: none. To make it work, 387 | # install the python-enchant package. 388 | spelling-dict= 389 | 390 | # List of comma separated words that should not be checked. 391 | spelling-ignore-words= 392 | 393 | # A path to a file that contains the private dictionary; one word per line. 394 | spelling-private-dict-file= 395 | 396 | # Tells whether to store unknown words to the private dictionary (see the 397 | # --spelling-private-dict-file option) instead of raising a message. 398 | spelling-store-unknown-words=no 399 | 400 | 401 | [STRING] 402 | 403 | # This flag controls whether inconsistent-quotes generates a warning when the 404 | # character used as a quote delimiter is used inconsistently within a module. 405 | check-quote-consistency=no 406 | 407 | # This flag controls whether the implicit-str-concat should generate a warning 408 | # on implicit string concatenation in sequences defined over several lines. 409 | check-str-concat-over-line-jumps=no 410 | 411 | 412 | [TYPECHECK] 413 | 414 | # List of decorators that produce context managers, such as 415 | # contextlib.contextmanager. Add to this list to register other decorators that 416 | # produce valid context managers. 417 | contextmanager-decorators=contextlib.contextmanager 418 | 419 | # List of members which are set dynamically and missed by pylint inference 420 | # system, and so shouldn't trigger E1101 when accessed. Python regular 421 | # expressions are accepted. 422 | generated-members= 423 | 424 | # Tells whether missing members accessed in mixin class should be ignored. A 425 | # mixin class is detected if its name ends with "mixin" (case insensitive). 426 | ignore-mixin-members=yes 427 | 428 | # Tells whether to warn about missing members when the owner of the attribute 429 | # is inferred to be None. 430 | ignore-none=yes 431 | 432 | # This flag controls whether pylint should warn about no-member and similar 433 | # checks whenever an opaque object is returned when inferring. The inference 434 | # can return multiple potential results while evaluating a Python object, but 435 | # some branches might not be evaluated, which results in partial inference. In 436 | # that case, it might be useful to still emit no-member and other checks for 437 | # the rest of the inferred objects. 438 | ignore-on-opaque-inference=yes 439 | 440 | # List of class names for which member attributes should not be checked (useful 441 | # for classes with dynamically set attributes). This supports the use of 442 | # qualified names. 443 | ignored-classes=optparse.Values,thread._local,_thread._local 444 | 445 | # List of module names for which member attributes should not be checked 446 | # (useful for modules/projects where namespaces are manipulated during runtime 447 | # and thus existing member attributes cannot be deduced by static analysis). It 448 | # supports qualified module names, as well as Unix pattern matching. 449 | ignored-modules= 450 | 451 | # Show a hint with possible names when a member name was not found. The aspect 452 | # of finding the hint is based on edit distance. 453 | missing-member-hint=yes 454 | 455 | # The minimum edit distance a name should have in order to be considered a 456 | # similar match for a missing member name. 457 | missing-member-hint-distance=1 458 | 459 | # The total number of similar names that should be taken in consideration when 460 | # showing a hint for a missing member. 461 | missing-member-max-choices=1 462 | 463 | # List of decorators that change the signature of a decorated function. 464 | signature-mutators= 465 | 466 | 467 | [VARIABLES] 468 | 469 | # List of additional names supposed to be defined in builtins. Remember that 470 | # you should avoid defining new builtins when possible. 471 | additional-builtins= 472 | 473 | # Tells whether unused global variables should be treated as a violation. 474 | allow-global-unused-variables=yes 475 | 476 | # List of strings which can identify a callback function by name. A callback 477 | # name must start or end with one of those strings. 478 | callbacks=cb_, 479 | _cb 480 | 481 | # A regular expression matching the name of dummy variables (i.e. expected to 482 | # not be used). 483 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 484 | 485 | # Argument names that match this expression will be ignored. Default to name 486 | # with leading underscore. 487 | ignored-argument-names=_.*|^ignored_|^unused_ 488 | 489 | # Tells whether we should check for unused import in __init__ files. 490 | init-import=no 491 | 492 | # List of qualified module names which can have objects that can redefine 493 | # builtins. 494 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 495 | 496 | 497 | [CLASSES] 498 | 499 | # List of method names used to declare (i.e. assign) instance attributes. 500 | defining-attr-methods=__init__, 501 | __new__, 502 | setUp, 503 | __post_init__ 504 | 505 | # List of member names, which should be excluded from the protected access 506 | # warning. 507 | exclude-protected=_asdict, 508 | _fields, 509 | _replace, 510 | _source, 511 | _make 512 | 513 | # List of valid names for the first argument in a class method. 514 | valid-classmethod-first-arg=cls 515 | 516 | # List of valid names for the first argument in a metaclass class method. 517 | valid-metaclass-classmethod-first-arg=cls 518 | 519 | 520 | [DESIGN] 521 | 522 | # Maximum number of arguments for function / method. 523 | max-args=5 524 | 525 | # Maximum number of attributes for a class (see R0902). 526 | max-attributes=7 527 | 528 | # Maximum number of boolean expressions in an if statement (see R0916). 529 | max-bool-expr=5 530 | 531 | # Maximum number of branch for function / method body. 532 | max-branches=12 533 | 534 | # Maximum number of locals for function / method body. 535 | max-locals=15 536 | 537 | # Maximum number of parents for a class (see R0901). 538 | max-parents=7 539 | 540 | # Maximum number of public methods for a class (see R0904). 541 | max-public-methods=20 542 | 543 | # Maximum number of return / yield for function / method body. 544 | max-returns=6 545 | 546 | # Maximum number of statements in function / method body. 547 | max-statements=50 548 | 549 | # Minimum number of public methods for a class (see R0903). 550 | min-public-methods=2 551 | 552 | 553 | [IMPORTS] 554 | 555 | # List of modules that can be imported at any level, not just the top level 556 | # one. 557 | allow-any-import-level= 558 | 559 | # Allow wildcard imports from modules that define __all__. 560 | allow-wildcard-with-all=no 561 | 562 | # Analyse import fallback blocks. This can be used to support both Python 2 and 563 | # 3 compatible code, which means that the block might have code that exists 564 | # only in one or another interpreter, leading to false positives when analysed. 565 | analyse-fallback-blocks=no 566 | 567 | # Deprecated modules which should not be used, separated by a comma. 568 | deprecated-modules=optparse,tkinter.tix 569 | 570 | # Create a graph of external dependencies in the given file (report RP0402 must 571 | # not be disabled). 572 | ext-import-graph= 573 | 574 | # Create a graph of every (i.e. internal and external) dependencies in the 575 | # given file (report RP0402 must not be disabled). 576 | import-graph= 577 | 578 | # Create a graph of internal dependencies in the given file (report RP0402 must 579 | # not be disabled). 580 | int-import-graph= 581 | 582 | # Force import order to recognize a module as part of the standard 583 | # compatibility libraries. 584 | known-standard-library= 585 | 586 | # Force import order to recognize a module as part of a third party library. 587 | known-third-party=enchant 588 | 589 | # Couples of modules and preferred modules, separated by a comma. 590 | preferred-modules= 591 | 592 | 593 | [EXCEPTIONS] 594 | 595 | # Exceptions that will emit a warning when being caught. Defaults to 596 | # "BaseException, Exception". 597 | overgeneral-exceptions=BaseException, 598 | Exception 599 | -------------------------------------------------------------------------------- /moonbag/cryptocompare/cryptocomp.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import os 3 | from moonbag.cryptocompare._client import CryptoCompareClient 4 | from moonbag.common.keys import CC_API_KEY 5 | from moonbag.common.utils import wrap_text_in_df 6 | from moonbag.cryptocompare.utils import create_dct_mapping_from_df 7 | import logging 8 | import textwrap 9 | 10 | logger = logging.getLogger("cmc") 11 | 12 | 13 | class CryptoCompare(CryptoCompareClient): 14 | def __init__(self, api_key=CC_API_KEY): 15 | super().__init__(api_key) 16 | self.api_key = api_key 17 | try: 18 | self.coin_list = self.get_all_coins_list() 19 | self.coin_mapping = create_dct_mapping_from_df( 20 | self.coin_list, "Symbol", "Id" 21 | ) 22 | except TypeError: 23 | logger.warning("Wrong API KEY, Please ") 24 | 25 | def get_price(self, symbol="BTC", currency="USD", **kwargs): 26 | data = self._get_price(symbol, currency, **kwargs) 27 | if "Response" in data and data["Response"] == "Error": 28 | return pd.DataFrame() 29 | data = data["RAW"][symbol][currency] 30 | columns = [ 31 | "FROMSYMBOL", 32 | "TOSYMBOL", 33 | "PRICE", 34 | "MEDIAN", 35 | "VOLUMEDAY", 36 | "VOLUME24HOUR", 37 | "OPENDAY", 38 | "OPEN24HOUR", 39 | "HIGH24HOUR", 40 | "LOW24HOUR", 41 | "MKTCAP", 42 | "SUPPLY", 43 | "TOTALVOLUME24H", 44 | "CHANGEDAY" "CHANGEPCTDAY", 45 | "CHANGEHOUR" "CHANGEPCTHOUR", 46 | "CHANGE24HOUR", 47 | "CHANGEPCT24HOUR", 48 | ] 49 | data = {k: v for k, v in data.items() if k in columns} 50 | df = pd.Series(data).to_frame().reset_index() 51 | df.columns = ["Metric", "Value"] 52 | return df 53 | 54 | def get_top_list_by_market_cap(self, currency="USD", limit=100, **kwargs): 55 | limit = 10 if limit < 10 else limit 56 | data = self._get_top_list_by_market_cap(currency, limit, **kwargs)["Data"] 57 | data = [{"CoinInfo": d.get("CoinInfo"), "RAW": d.get("RAW")} for d in data] 58 | df = pd.json_normalize(data) 59 | df.columns = [col.split(".")[-1] for col in list(df.columns)] 60 | df = wrap_text_in_df(df) 61 | 62 | df.rename( 63 | columns={ 64 | "TechnologyAdoptionRating": "TechRating", 65 | "MarketPerformanceRating": "MarketRating", 66 | "AssetLaunchDate": "Launched", 67 | "TOSYMBOL": "ToSymbol", 68 | "PRICE": "Price", 69 | "MEDIAN": "Median", 70 | "MKTCAP": "MarketCap", 71 | "SUPPLY": "Supply", 72 | "CHANGEPCT24HOUR": "% change 24h", 73 | "CHANGEPCTHOUR": "% change 1h", 74 | "TOTALVOLUME24H": "Volume 24h", 75 | }, 76 | inplace=True, 77 | ) 78 | columns = [ 79 | "Name", 80 | "FullName", 81 | "ProofType", 82 | "Rating", 83 | "Launched", 84 | "ToSymbol", 85 | "Price", 86 | "MarketCap", 87 | "Supply", 88 | "% change 24h", 89 | "% change 1h", 90 | "Volume 24h", 91 | ] 92 | return df[columns] 93 | 94 | def get_top_exchanges(self, symbol="BTC", currency="USD", limit=100, **kwargs): 95 | try: 96 | data = self._get_top_exchanges(symbol, currency, limit, **kwargs)["Data"][ 97 | "Exchanges" 98 | ] 99 | except KeyError as e: 100 | logger.error(e) 101 | return pd.DataFrame() 102 | 103 | df = pd.json_normalize(data) 104 | columns = [ 105 | "MARKET", 106 | "FROMSYMBOL", 107 | "TOSYMBOL", 108 | "PRICE", 109 | "VOLUME24HOUR", 110 | "OPENDAY", 111 | "HIGHDAY", 112 | "LOWDAY", 113 | "CHANGEPCT24HOUR", 114 | "CHANGEPCTHOUR", 115 | ] 116 | 117 | return df[columns] 118 | 119 | def get_exchanges_top_symbols_by_volume( 120 | self, exchange="Binance", limit=100, **kwargs 121 | ): 122 | data = self._get_exchanges_top_symbols_by_volume(exchange, limit, **kwargs)[ 123 | "Data" 124 | ] 125 | df = pd.DataFrame(data) 126 | try: 127 | df["volume"] = df["volume"].apply(lambda x: float(x)) 128 | df = df[df["volume"] > 0] 129 | except KeyError as e: 130 | logger.log(2, e) 131 | return df 132 | 133 | def get_top_list_by_pair_volume(self, currency="USD", limit=100, **kwargs): 134 | data = self._get_top_list_by_pair_volume(currency, limit, **kwargs)["Data"] 135 | df = pd.DataFrame(data) 136 | columns = ["SYMBOL", "NAME", "FULLNAME", "SUPPLY", "VOLUME24HOURTO"] 137 | return df[columns] 138 | 139 | def get_top_of_trading_pairs(self, symbol="ETH", limit=50, **kwargs): 140 | data = self._get_top_of_trading_pairs(symbol, limit, **kwargs)["Data"] 141 | return pd.DataFrame(data) 142 | 143 | def get_latest_social_coin_stats(self, coin_id=7605, **kwargs): 144 | data = self._get_latest_social_coin_stats(coin_id, **kwargs)["Data"] 145 | social_stats = {} 146 | 147 | general = pd.Series(data.get("General")).to_frame().reset_index() 148 | for social in ["Twitter", "Reddit", "Facebook"]: 149 | social_stats[social] = data.get(social) 150 | 151 | repo = data["CodeRepository"] 152 | repo_dct = {} 153 | if "List" in repo and len(repo["List"]) > 0: 154 | for k, v in repo["List"][0].items(): 155 | if not isinstance(v, (list, dict, set)): 156 | repo_dct[k] = v 157 | social_stats["repository"] = repo_dct 158 | 159 | df = pd.json_normalize(social_stats) 160 | df.columns = [col.replace(".", "_").lower() for col in list(df.columns)] 161 | for col in [ 162 | "twitter_account_creation", 163 | "reddit_community_creation", 164 | "repository_last_update", 165 | "repository_created_at", 166 | "repository_last_push", 167 | ]: 168 | try: 169 | df[col] = pd.to_datetime(df[col], unit="s") 170 | except KeyError as e: 171 | logger.log(2, e) 172 | df = df.T.reset_index() 173 | df = pd.concat([general, df]) 174 | df.columns = ["Metric", "Value"] 175 | return df 176 | 177 | def get_historical_social_stats( 178 | self, coin_id=7605, limit=150, aggregate=7, **kwargs 179 | ): 180 | data = self._get_historical_social_stats(coin_id, limit, aggregate, **kwargs)[ 181 | "Data" 182 | ] 183 | df = pd.DataFrame(data) 184 | df["time"] = pd.to_datetime(df["time"], unit="s") 185 | df["time"] = df["time"].dt.strftime("%Y/%m/%d") 186 | df.drop( 187 | [ 188 | "comments", 189 | "posts", 190 | "followers", 191 | "points", 192 | "overview_page_views", 193 | "analysis_page_views", 194 | "markets_page_views", 195 | "charts_page_views", 196 | "trades_page_views", 197 | "forum_page_views", 198 | "influence_page_views", 199 | "total_page_views", 200 | ], 201 | axis=1, 202 | inplace=True, 203 | ) 204 | df = wrap_text_in_df(df, w=40) 205 | cols = [ 206 | "time", 207 | "fb_likes", 208 | "twitter_followers", 209 | "reddit_subscribers", 210 | "reddit_active_users", 211 | "reddit_posts_per_day", 212 | "reddit_comments_per_hour", 213 | "reddit_comments_per_day", 214 | "code_repo_stars", 215 | "code_repo_forks", 216 | "code_repo_subscribers", 217 | "code_repo_closed_issues", 218 | "code_repo_contributors", 219 | ] 220 | df = df[cols] 221 | df.columns = [ 222 | textwrap.fill(c.replace("_", " "), 13, break_long_words=False) 223 | for c in list(df.columns) 224 | ] 225 | return df 226 | 227 | def get_latest_news(self, lang="EN", sort_order="latest", **kwargs): 228 | data = self._get_latest_news(lang, sort_order, **kwargs)["Data"] 229 | df = pd.DataFrame(data) 230 | df.drop( 231 | ["upvotes", "downvotes", "lang", "source_info", "imageurl", "id", "url"], 232 | axis=1, 233 | inplace=True, 234 | ) 235 | df["published_on"] = pd.to_datetime(df["published_on"], unit="s") 236 | 237 | # 'tags','categories','guid','body' 238 | 239 | df = df[["published_on", "title", "source", "guid"]] 240 | 241 | df = df.applymap( 242 | lambda x: "\n".join(textwrap.wrap(x, width=66)) if isinstance(x, str) else x 243 | ) 244 | return df 245 | 246 | def get_blockchain_available_coins_list(self): 247 | data = self._get_blockchain_available_coins_list()["Data"] 248 | df = pd.DataFrame(data).T 249 | df["data_available_from"] = pd.to_datetime(df["data_available_from"], unit="s") 250 | return df 251 | 252 | @property 253 | def blockchain_coins_list(self): 254 | return self.get_blockchain_available_coins_list()["symbol"].to_list() 255 | 256 | def get_all_coins_list(self, summary="true", **kwargs): 257 | data = self._get_all_coins_list(summary, **kwargs)["Data"] 258 | return pd.DataFrame(data).T[["Id", "Symbol", "FullName"]] 259 | 260 | def get_historical_day_prices( 261 | self, symbol="BTC", currency="USD", limit=365, **kwargs 262 | ): 263 | data = self._get_historical_day_prices(symbol, currency, limit, **kwargs)[ 264 | "Data" 265 | ] 266 | df = pd.DataFrame(data) 267 | df.drop( 268 | ["volumefrom", "conversionType", "conversionSymbol"], axis=1, inplace=True 269 | ) 270 | df["time"] = pd.to_datetime(df["time"], unit="s") 271 | return df 272 | 273 | def get_historical_hour_prices( 274 | self, symbol="BTC", currency="USD", limit=60 * 24, **kwargs 275 | ): 276 | data = self._get_historical_hour_prices(symbol, currency, limit, **kwargs)[ 277 | "Data" 278 | ] 279 | df = pd.DataFrame(data) 280 | df.drop( 281 | ["volumefrom", "conversionType", "conversionSymbol"], axis=1, inplace=True 282 | ) 283 | df["time"] = pd.to_datetime(df["time"], unit="s") 284 | return df 285 | 286 | def get_historical_minutes_prices( 287 | self, symbol="BTC", currency="USD", limit=60 * 24, **kwargs 288 | ): 289 | data = self._get_historical_minutes_prices(symbol, currency, limit, **kwargs)[ 290 | "Data" 291 | ] 292 | df = pd.DataFrame(data) 293 | df.drop( 294 | ["volumefrom", "conversionType", "conversionSymbol"], axis=1, inplace=True 295 | ) 296 | df["time"] = pd.to_datetime(df["time"], unit="s") 297 | return df 298 | 299 | def get_daily_exchange_volume( 300 | self, currency="USD", exchange="CCCAGG", limit=365, **kwargs 301 | ): 302 | data = self._get_daily_exchange_volume(currency, exchange, limit, **kwargs)[ 303 | "Data" 304 | ] 305 | df = pd.DataFrame(data) 306 | df["time"] = pd.to_datetime(df["time"], unit="s") 307 | return df 308 | 309 | def get_hourly_exchange_volume( 310 | self, currency="USD", exchange="CCCAGG", limit=60 * 24, **kwargs 311 | ): 312 | data = self._get_hourly_exchange_volume(currency, exchange, limit, **kwargs)[ 313 | "Data" 314 | ] 315 | df = pd.DataFrame(data) 316 | df["time"] = pd.to_datetime(df["time"], unit="s") 317 | return df 318 | 319 | def get_daily_symbol_volume( 320 | self, symbol="BTC", currency="USD", limit=365, **kwargs 321 | ): 322 | data = self._get_daily_symbol_volume(symbol, currency, limit, **kwargs)["Data"] 323 | df = pd.DataFrame(data) 324 | df["time"] = pd.to_datetime(df["time"], unit="s") 325 | return df[["time", "top_tier_volume_total", "total_volume_total"]] 326 | 327 | def get_hourly_symbol_volume( 328 | self, symbol="BTC", currency="USD", limit=365, **kwargs 329 | ): 330 | data = self._get_hourly_symbol_volume(symbol, currency, limit, **kwargs)["Data"] 331 | df = pd.DataFrame(data) 332 | df["time"] = pd.to_datetime(df["time"], unit="s") 333 | return df[["time", "top_tier_volume_total", "total_volume_total"]] 334 | 335 | def get_latest_blockchain_data(self, symbol="BTC", **kwargs): 336 | try: 337 | data = self._get_latest_blockchain_data(symbol, **kwargs)["Data"] 338 | df = pd.Series(data) 339 | df["time"] = pd.to_datetime(df["time"], unit="s") 340 | df = df.to_frame().reset_index() 341 | df.columns = ["Metric", "Value"] 342 | return df 343 | except KeyError as e: 344 | logger.log(2, e) 345 | return pd.DataFrame() 346 | 347 | def get_historical_blockchain_data(self, symbol="ETH", limit=365, **kwargs): 348 | try: 349 | data = self._get_historical_blockchain_data(symbol, limit, **kwargs)[ 350 | "Data" 351 | ]["Data"] 352 | df = pd.DataFrame(data) 353 | df["time"] = pd.to_datetime(df["time"], unit="s") 354 | df.drop( 355 | [ 356 | "id", 357 | "block_height", 358 | "hashrate", 359 | "difficulty", 360 | "block_time", 361 | "block_size", 362 | ], 363 | axis=1, 364 | inplace=True, 365 | ) 366 | 367 | return df 368 | except KeyError as e: 369 | logger.log(2, e) 370 | return pd.DataFrame() 371 | 372 | def get_latest_trading_signals(self, symbol="ETH", **kwargs): 373 | data = self._get_latest_trading_signals(symbol, **kwargs)["Data"] 374 | df = pd.DataFrame(data) 375 | try: 376 | df.drop(["id", "partner_symbol"], inplace=True, axis=1) 377 | df["time"] = pd.to_datetime(df["time"], unit="s") 378 | except KeyError as e: 379 | logger.log(2, e) 380 | return df 381 | 382 | def get_order_books_exchanges(self, **kwargs): 383 | data = self._get_order_books_exchanges(**kwargs)["Data"] 384 | df = pd.DataFrame(data).T 385 | df["orderBookAvailability"] = df["orderBookAvailability"].apply( 386 | lambda x: ", ".join(x) if isinstance(x, list) else x 387 | ) 388 | return df 389 | 390 | def get_order_book_top( 391 | self, symbol="LUNA", to_symbol="BTC", exchange="binance", **kwargs 392 | ): 393 | 394 | r = self._get_order_book_top( 395 | symbol.upper(), to_symbol.upper(), exchange.capitalize(), **kwargs 396 | ) 397 | if 'Error' in r['Response']: 398 | print(r['Message']) 399 | 400 | data = r["Data"] 401 | if not data: 402 | return pd.DataFrame() 403 | df = pd.json_normalize(data["RAW"]) 404 | df.columns = [c.replace(".", "_") for c in df.columns] 405 | df.drop(df.columns[0], axis=1, inplace=True) 406 | return df 407 | 408 | def get_order_book_snapshot( 409 | self, symbol="LUNA", to_symbol="BTC", exchange="binance", **kwargs 410 | ): 411 | data = self._get_order_book_snapshot(symbol, to_symbol, exchange, **kwargs)[ 412 | "Data" 413 | ] 414 | bid = pd.DataFrame(data.pop("BID")) 415 | ask = pd.DataFrame(data.pop("ASK")) 416 | bid.columns = ["Bid_price", "Bid_Quantity"] 417 | ask.columns = ["Ask_price", "Ask_Quantity"] 418 | df = pd.concat([ask, bid], axis=1) 419 | df["Exchange"] = data.get("M") 420 | df["From_Symbol"] = data.get("FSYM") 421 | df["To_Symbol"] = data.get("FSYM") 422 | return df 423 | 424 | def get_all_exchanges_info(self, symbol="BTC", **kwargs): 425 | data = self._get_all_exchanges_info(symbol, **kwargs)["Data"] 426 | return pd.DataFrame(data).T 427 | 428 | def get_all_exchanges_names(self): 429 | exchanges = list( 430 | self._get_all_exchanges_and_trading_pairs()["Data"]["exchanges"].keys() 431 | ) 432 | df = pd.Series(exchanges).to_frame().reset_index() 433 | df.columns = ["Index", "Name"] 434 | return df 435 | 436 | def get_all_wallet_info(self, **kwargs): 437 | data = self._get_all_wallet_info(**kwargs)["Data"] 438 | df = pd.DataFrame(data).T 439 | cols = [ 440 | "Name", 441 | # "Security", 442 | "Anonymity", 443 | "EaseOfUse", 444 | # "WalletFeatures", 445 | "Coins", 446 | # "Platforms", 447 | # "SourceCodeUrl", 448 | "Avg", 449 | "Votes", 450 | ] 451 | df["Avg"] = df["Rating"].apply(lambda x: x.get("Avg")) 452 | df["Votes"] = df["Rating"].apply(lambda x: x.get("TotalUsers")) 453 | joins = ["WalletFeatures", "Platforms", "Coins"] 454 | for col in joins: 455 | df[col] = df[col].apply(lambda x: ", ".join(x)) 456 | 457 | df = df[cols].sort_values(by="Votes", ascending=False) 458 | df = df.applymap( 459 | lambda x: "\n".join(textwrap.wrap(x, width=85)) if isinstance(x, str) else x 460 | ) 461 | return df 462 | 463 | def get_all_gambling_info(self, **kwargs): 464 | data = self._get_all_gambling_info(**kwargs)["Data"] 465 | df = pd.DataFrame(data).T 466 | columns = [ 467 | "Name", 468 | "GameTypes", 469 | "Coins", 470 | # "GamblingFeatures", 471 | # "Platforms", 472 | "Twitter", 473 | "Reddit", 474 | "Avg", 475 | "Votes", 476 | ] 477 | joins = [ 478 | "GameTypes", 479 | "Coins", 480 | ] 481 | df["Avg"] = df["Rating"].apply(lambda x: x.get("Avg")) 482 | df["Votes"] = df["Rating"].apply(lambda x: x.get("TotalUsers")) 483 | 484 | for col in joins: 485 | df[col] = df[col].apply(lambda x: ", ".join(x)) 486 | 487 | df = df[columns].sort_values(by="Votes", ascending=False) 488 | 489 | df = df.applymap( 490 | lambda x: "\n".join(textwrap.wrap(x, width=55)) if isinstance(x, str) else x 491 | ) 492 | return df 493 | 494 | def get_recommended_wallets(self, symbol="BTC", **kwargs): 495 | try: 496 | data = self._get_recommendations(symbol, **kwargs)["Data"]["wallets"] 497 | df = pd.DataFrame(data).T 498 | cols = [ 499 | "Name", 500 | "Security", 501 | "Anonymity", 502 | "EaseOfUse", 503 | # "WalletFeatures", 504 | "Coins", 505 | # "Platforms", 506 | "SourceCodeUrl", 507 | "Avg", 508 | "Votes", 509 | ] 510 | df["Avg"] = df["Rating"].apply(lambda x: x.get("Avg")) 511 | df["Votes"] = df["Rating"].apply(lambda x: x.get("TotalUsers")) 512 | joins = ["WalletFeatures", "Platforms", "Coins"] 513 | for col in joins: 514 | df[col] = df[col].apply(lambda x: ", ".join(x)) 515 | df = wrap_text_in_df(df, w=40) 516 | return df[cols].sort_values(by="Votes", ascending=False) 517 | except KeyError as e: 518 | logger.log(2, e) 519 | return pd.DataFrame() 520 | 521 | def get_recommended_exchanges(self, symbol="BTC", **kwargs): 522 | try: 523 | data = self._get_recommendations(symbol, **kwargs)["Data"]["exchanges"] 524 | df = pd.DataFrame(data).T 525 | columns = [ 526 | "Name", 527 | "ItemType", 528 | "CentralizationType", 529 | "GradePoints", 530 | "Grade", 531 | "Country", 532 | "DepositMethods", 533 | "Avg", 534 | "Votes", 535 | ] 536 | for col in ["FullAddress", "DepositMethods", "WithdrawalMethods"]: 537 | df[col] = df[col].apply(lambda x: x.replace("\n\n", ", ")) 538 | df[col] = df[col].apply(lambda x: x.replace(",\n", ", ")) 539 | df[col] = df[col].apply(lambda x: x.replace("\n", ", ")) 540 | df["Avg"] = df["Rating"].apply(lambda x: x.get("Avg")) 541 | df["Votes"] = df["Rating"].apply(lambda x: x.get("TotalUsers")) 542 | df["ItemType"] = df["ItemType"].apply(lambda x: ", ".join(x)) 543 | df = wrap_text_in_df(df, w=40) 544 | return df[columns].sort_values(by="Votes", ascending=False) 545 | except KeyError as e: 546 | logger.log(2, e) 547 | return pd.DataFrame() 548 | --------------------------------------------------------------------------------