diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000000..29a1f38c2d3
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,38 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v3.4.0
+ hooks:
+ - id: check-yaml
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
+ - repo: https://github.com/psf/black
+ rev: 21.4b2
+ hooks:
+ - id: black
+ - repo: https://github.com/pycqa/flake8
+ rev: 3.9.1
+ hooks:
+ - id: flake8
+ entry: flake8 --max-line-length=122 --ignore=E203,W503
+ - repo: https://github.com/codespell-project/codespell
+ rev: v2.0.0
+ hooks:
+ - id: codespell
+ entry: codespell
+ args: [ '--ignore-words-list="mape","ba","hist"', '--quiet-level=2', '--skip=".git,*.yaml,*.json,./tests"' ]
+ - repo: https://github.com/pre-commit/mirrors-mypy
+ rev: 'v0.812'
+ hooks:
+ - id: mypy
+ args: [ --ignore-missing-imports ]
+ - repo: https://github.com/pre-commit/mirrors-pylint
+ rev: 'v3.0.0a3'
+ hooks:
+ - id: pylint
+ entry: pylint terminal.py gamestonk_terminal tests
+ - repo: https://github.com/Yelp/detect-secrets
+ rev: v1.1.0
+ hooks:
+ - id: detect-secrets
+ args: [ '--baseline', '.secrets.baseline' ]
+ exclude: package.lock.json
diff --git a/.secrets.baseline b/.secrets.baseline
new file mode 100644
index 00000000000..10a1fe2c470
--- /dev/null
+++ b/.secrets.baseline
@@ -0,0 +1,103 @@
+{
+ "version": "1.1.0",
+ "plugins_used": [
+ {
+ "name": "ArtifactoryDetector"
+ },
+ {
+ "name": "AWSKeyDetector"
+ },
+ {
+ "name": "AzureStorageKeyDetector"
+ },
+ {
+ "name": "Base64HighEntropyString",
+ "limit": 4.5
+ },
+ {
+ "name": "BasicAuthDetector"
+ },
+ {
+ "name": "CloudantDetector"
+ },
+ {
+ "name": "HexHighEntropyString",
+ "limit": 3.0
+ },
+ {
+ "name": "IbmCloudIamDetector"
+ },
+ {
+ "name": "IbmCosHmacDetector"
+ },
+ {
+ "name": "JwtTokenDetector"
+ },
+ {
+ "name": "KeywordDetector",
+ "keyword_exclude": ""
+ },
+ {
+ "name": "MailchimpDetector"
+ },
+ {
+ "name": "NpmDetector"
+ },
+ {
+ "name": "PrivateKeyDetector"
+ },
+ {
+ "name": "SlackDetector"
+ },
+ {
+ "name": "SoftlayerDetector"
+ },
+ {
+ "name": "SquareOAuthDetector"
+ },
+ {
+ "name": "StripeDetector"
+ },
+ {
+ "name": "TwilioKeyDetector"
+ }
+ ],
+ "filters_used": [
+ {
+ "path": "detect_secrets.filters.allowlist.is_line_allowlisted"
+ },
+ {
+ "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
+ "min_level": 2
+ },
+ {
+ "path": "detect_secrets.filters.heuristic.is_indirect_reference"
+ },
+ {
+ "path": "detect_secrets.filters.heuristic.is_likely_id_string"
+ },
+ {
+ "path": "detect_secrets.filters.heuristic.is_lock_file"
+ },
+ {
+ "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
+ },
+ {
+ "path": "detect_secrets.filters.heuristic.is_potential_uuid"
+ },
+ {
+ "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
+ },
+ {
+ "path": "detect_secrets.filters.heuristic.is_sequential_string"
+ },
+ {
+ "path": "detect_secrets.filters.heuristic.is_swagger_file"
+ },
+ {
+ "path": "detect_secrets.filters.heuristic.is_templated_secret"
+ }
+ ],
+ "results": {},
+ "generated_at": "2021-05-03T16:53:30Z"
+}
diff --git a/README.md b/README.md
index 4e558e73cf5..5d6ddd3a4e6 100644
--- a/README.md
+++ b/README.md
@@ -20,13 +20,13 @@
[](https://github.com/psf/black)

-
+[](https://twitter.com/gamestonkt)
+## stats
+```
+usage: stats
+```
+Sentiment stats which displays buzz, news score, articles last week, articles weekly average, bullish vs bearish percentages, sector average bullish percentage, and sector average news score. [Source: https://finnhub.io]
+
+
+
+
# REDDIT
## wsb
diff --git a/gamestonk_terminal/behavioural_analysis/ba_controller.py b/gamestonk_terminal/behavioural_analysis/ba_controller.py
index e8ae8f60aee..725bbdbfa55 100644
--- a/gamestonk_terminal/behavioural_analysis/ba_controller.py
+++ b/gamestonk_terminal/behavioural_analysis/ba_controller.py
@@ -14,6 +14,7 @@ from gamestonk_terminal.behavioural_analysis import (
reddit_view,
stocktwits_view,
finbrain_view,
+ finnhub_view,
)
@@ -41,6 +42,7 @@ class BehaviouralAnalysisController:
"queries",
"rise",
"finbrain",
+ "stats",
]
def __init__(self, ticker: str, start: datetime):
@@ -63,6 +65,7 @@ class BehaviouralAnalysisController:
print(" quit quit to abandon program")
print("")
print(" finbrain sentiment from 15+ major news headlines")
+ print(" stats sentiment stats including comparison with sector")
print("")
print("Reddit:")
print(
@@ -218,6 +221,10 @@ class BehaviouralAnalysisController:
"""Process finbrain command"""
finbrain_view.sentiment_analysis(other_args, self.ticker)
+ def call_stats(self, other_args: List[str]):
+ """Process stats command"""
+ finnhub_view.sentiment_stats(other_args, self.ticker)
+
def menu(ticker: str, start: datetime):
"""Behavioural Analysis Menu"""
diff --git a/gamestonk_terminal/behavioural_analysis/finnhub_view.py b/gamestonk_terminal/behavioural_analysis/finnhub_view.py
new file mode 100644
index 00000000000..f719184d096
--- /dev/null
+++ b/gamestonk_terminal/behavioural_analysis/finnhub_view.py
@@ -0,0 +1,82 @@
+import argparse
+from typing import List, Dict
+import requests
+from gamestonk_terminal import config_terminal as cfg
+from gamestonk_terminal.helper_funcs import (
+ parse_known_args_and_warn,
+)
+
+
+def get_sentiment_stats(ticker: str) -> Dict:
+ """Get sentiment stats
+
+ Parameters
+ ----------
+ ticker : str
+ Ticker to get sentiment stats
+
+ Returns
+ -------
+ Dict
+ Get sentiment stats
+ """
+ response = requests.get(
+ f"https://finnhub.io/api/v1/news-sentiment?symbol={ticker}&token={cfg.API_FINNHUB_KEY}"
+ )
+ if response.status_code == 200:
+ return response.json()
+
+ return {}
+
+
+def sentiment_stats(other_args: List[str], ticker: str):
+ """Sentiment stats which displays buzz, news score, articles last week, articles weekly average,
+ bullish vs bearish percentages, sector average bullish percentage, and sector average news score
+
+ Parameters
+ ----------
+ other_args : List[str]
+ Command line arguments to be processed with argparse
+ ticker : str
+ Ticker to get sentiment stats
+ """
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ prog="stats",
+ description="""
+ Sentiment stats which displays buzz, news score, articles last week, articles weekly average,
+ bullish vs bearish percentages, sector average bullish percentage, and sector average news score.
+ [Source: https://finnhub.io]
+ """,
+ )
+
+ try:
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return
+
+ d_stats = get_sentiment_stats(ticker)
+
+ if d_stats:
+ print(f"Buzz: {round(100*d_stats['buzz']['buzz'],2)} %")
+ print(f"News Score: {round(100*d_stats['companyNewsScore'],2)} %")
+ print("")
+ print(f"Articles Last Week: {d_stats['buzz']['articlesInLastWeek']}")
+ print(f"Articles Weekly Average: {d_stats['buzz']['weeklyAverage']}")
+ print("")
+ print(f"Bullish: {round(100*d_stats['sentiment']['bullishPercent'],2)} %")
+ print(f"Bearish: {round(100*d_stats['sentiment']['bearishPercent'],2)} %")
+ print("")
+ print(
+ f"Sector Average Bullish: {round(100*d_stats['sectorAverageBullishPercent'],2)} %"
+ )
+ print(
+ f"Sector Average News Score: {round(100*d_stats['sectorAverageNewsScore'],2)} %"
+ )
+ else:
+ print("No sentiment stats found.")
+
+ print("")
+
+ except Exception as e:
+ print(e, "\n")
diff --git a/gamestonk_terminal/config_terminal.py b/gamestonk_terminal/config_terminal.py
index dd4092eb45d..3685f88e7dd 100644
--- a/gamestonk_terminal/config_terminal.py
+++ b/gamestonk_terminal/config_terminal.py
@@ -33,7 +33,7 @@ API_TWITTER_SECRET_KEY = os.getenv("GT_API_TWITTER_SECRET_KEY") or "REPLACE_ME"
API_TWITTER_BEARER_TOKEN = os.getenv("GT_API_TWITTER_BEARER_TOKEN") or "REPLACE_ME"
# https://fred.stlouisfed.org/docs/api/api_key.html
-API_FRED_KEY = os.getenv("GT_FRED_API_KEY") or "REPLACE_ME"
+API_FRED_KEY = os.getenv("GT_API_FRED_KEY") or "REPLACE_ME"
# https://newsapi.org
API_NEWS_TOKEN = os.getenv("GT_API_NEWS_TOKEN") or "REPLACE_ME"
@@ -51,11 +51,18 @@ OANDA_ACCOUNT = os.getenv("GT_OANDA_ACCOUNT") or "REPLACE ME"
OANDA_TOKEN = os.getenv("GT_OANDA_TOKEN") or "REPLACE ME"
# https://tradier.com/products/market-data-api
-TRADIER_TOKEN = os.getenv("GT_TRADIER_TOKEN") or "REPLACE_ME"
+TRADIER_TOKEN = os.getenv("GT_API_TRADIER_TOKEN") or "REPLACE_ME"
# Selenium Webbrowser drivers can be found at https://selenium-python.readthedocs.io/installation.html
WEBDRIVER_TO_USE = "chrome"
PATH_TO_SELENIUM_DRIVER = None # Replace with "PATH"
# https://coinmarketcap.com/api/
-COINMARKETCAP_KEY = os.getenv("GT_CMC_API_KEY") or "REPLACE_ME"
+API_CMC_KEY = os.getenv("GT_API_CMC_KEY") or "REPLACE_ME"
+
+# https://www.binance.com/en/
+API_BINANCE_KEY = os.getenv("GT_API_BINANCE_KEY") or "REPLACE_ME"
+API_BINANCE_SECRET = os.getenv("GT_API_BINANCE_SECRET") or "REPLACE_ME"
+
+# https://finnhub.io
+API_FINNHUB_KEY = os.getenv("GT_API_FINNHUB_KEY") or "REPLACE_ME"
diff --git a/gamestonk_terminal/cryptocurrency/README.md b/gamestonk_terminal/cryptocurrency/README.md
index 4a1b7b48fea..da8832da854 100644
--- a/gamestonk_terminal/cryptocurrency/README.md
+++ b/gamestonk_terminal/cryptocurrency/README.md
@@ -2,14 +2,29 @@
This menu aims to explore crypto world, and the usage of the following commands along with an example will be exploited below.
+[COINGECKO](#COINGECKO)
* [load](#load)
* load a given coin vs a given currency [CoinGecko]
* [view](#view)
* plot the loaded crypto data
+* [trend](#trend)
+ * show top 7 trending coins
+
+[COINMARKETCAP](#COINMARKETCAP)
* [top](#top)
* view top coins from coinmarketcap [coinmarketcap.com]
+[BINANCE](#BINANCE)
+* [select](#select)
+ * select coin/currency to use
+* [book](#book)
+ * show order book
+* [candle](#candle)
+ * get klines/candles and plot
+* [balance](#balance)
+ * show coin balance
+# COINGECKO
## load
````
@@ -36,6 +51,17 @@ Plot the loaded crypto data.

+## trend
+````
+usage: trend
+````
+Print the top 7 trending coins from coingecko.
+
+
+
+
+# COINMARKETCAP
+
## top
````
@@ -50,3 +76,48 @@ This command displays the top n cryptocurrencies from coinmarketcap.com.
+# BINANCE
+
+## select
+````
+usage: select [-c --coin] [-q --quote]
+````
+
+Select a coin/currency to the current object. Note that in binance, the exchange "ticker" is usually COINCURR, such as BTCEUR for BTC to EURO. There is no USD, but it uses a coin tethered to the USD (USDT), which is the default. Some symbols are a combination of coins, as `ETHBTC` is a valid symbol
+* -c/--coin Coin to load. If not specified, BTC will be loaded to prevent errors later.
+* -q/--quote Quote currency. Defaults to `USDT` (which is 1-to-1 with USD)
+
+
+## book
+````
+usage: book -l/--limit
+````
+
+Gets and shows the order book for the given coin/currency. Shows the cumulative amount of orders.
+
+* -l/--limit Number of orders to get on each side. One of [5,10,20,50,100,500,1000,5000]. Defaults to 100.
+
+
+
+
+## candle
+````
+usage: candle [-i --interval] [-l --limit]
+````
+
+Show candle chart for loaded coin/currency.
+
+* -i/--interval. Interval for candles. One of [1day,3day,1hour,2hour,4hour,6hour,8hour,12hour,1week,1min,3min,5min,15min,30min,1month]. Defaults to 1day.
+* -l/--limit. Number of candles to get. Defaults to 100.
+
+
+
+
+## balance
+````
+usage: balance
+````
+
+Shows the current holding balance in your account.
+
+
diff --git a/gamestonk_terminal/cryptocurrency/binance_model.py b/gamestonk_terminal/cryptocurrency/binance_model.py
new file mode 100644
index 00000000000..7bfad6acd6f
--- /dev/null
+++ b/gamestonk_terminal/cryptocurrency/binance_model.py
@@ -0,0 +1,259 @@
+"""Binance model"""
+__docformat__ = "numpy"
+
+import argparse
+from typing import List
+import pandas as pd
+import numpy as np
+from binance.client import Client
+from binance.exceptions import BinanceAPIException
+from tabulate import tabulate
+from gamestonk_terminal.main_helper import parse_known_args_and_warn
+import gamestonk_terminal.config_terminal as cfg
+from gamestonk_terminal.cryptocurrency.binance_view import plot_order_book, plot_candles
+
+
+def check_valid_binance_str(symbol: str) -> str:
+ """Check if symbol is in defined binance"""
+ client = Client(cfg.API_BINANCE_KEY, cfg.API_BINANCE_SECRET)
+ try:
+ client.get_avg_price(symbol=symbol.upper())
+ return symbol.upper()
+ except BinanceAPIException as e:
+ raise argparse.ArgumentTypeError(
+ f"{symbol} is not a valid binance symbol"
+ ) from e
+
+
+# pylint: disable=inconsistent-return-statements
+def select_binance_coin(other_args: List[str]):
+ """Define current_coin from binance
+
+ Parameters
+ ----------
+ other_args: List[str]
+ Argparse arguments
+
+ Returns
+ -------
+ coin: str
+ Coin that is defined on binance
+ """
+ parser = argparse.ArgumentParser(
+ prog="select",
+ add_help=False,
+ description="Define the coin to be used from binance",
+ )
+ parser.add_argument(
+ "-c", "--coin", help="Coin to get", dest="coin", type=str, default="BTC"
+ )
+ parser.add_argument(
+ "-q",
+ "--quote",
+ help="Quote currency (what to view coin vs)",
+ dest="quote",
+ type=str,
+ default="USDT",
+ )
+ try:
+ if other_args:
+ if "-" not in other_args[0]:
+ other_args.insert(0, "-c")
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return None, None
+
+ coin = ns_parser.coin + ns_parser.quote
+ if check_valid_binance_str(coin):
+ print(f"{ns_parser.coin.upper()} loaded vs {ns_parser.quote.upper()}")
+ return ns_parser.coin.upper(), ns_parser.quote.upper()
+
+ except Exception as e:
+ print(e, "\n")
+ return None, None
+
+
+def order_book(other_args: List[str], coin: str, currency: str):
+ """Get order book for currency
+
+ Parameters
+ ----------
+ other_args: List[str]
+ Argparse arguments
+ coin: str
+ Coin to get symbol of
+ currency : str
+ Currency against which to check symbol
+ """
+ limit_list = [5, 10, 20, 50, 100, 500, 1000, 5000]
+ parser = argparse.ArgumentParser(
+ prog="book", add_help=False, description="Get the order book for selected coin"
+ )
+ parser.add_argument(
+ "-l",
+ "--limit",
+ dest="limit",
+ help="Limit parameter. Adjusts the weight",
+ default=100,
+ type=int,
+ choices=limit_list,
+ )
+
+ try:
+ if not coin or not currency:
+ print("Coin needs to be selected prior to this command\n")
+ return
+
+ coin += currency
+
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return
+ client = Client(cfg.API_BINANCE_KEY, cfg.API_BINANCE_SECRET)
+ market_book = client.get_order_book(symbol=coin, limit=ns_parser.limit)
+ bids = np.asarray(market_book["bids"], dtype=float)
+ asks = np.asarray(market_book["asks"], dtype=float)
+ bids = np.insert(bids, 2, bids[:, 1].cumsum(), axis=1)
+ asks = np.insert(asks, 2, np.flipud(asks[:, 1]).cumsum(), axis=1)
+ plot_order_book(bids, asks, coin)
+
+ except Exception as e:
+ print(e, "\n")
+
+
+def show_candles(other_args: List[str], coin: str, currency: str):
+ """Get klines/candles for coin
+
+ Parameters
+ ----------
+ other_args: List[str]
+ Argparse arguments
+ coin: str
+ Coin to get symbol of
+ currency : str
+ Currency against which to check symbol
+ """
+ client = Client(cfg.API_BINANCE_KEY, cfg.API_BINANCE_SECRET)
+
+ interval_map = {
+ "1day": client.KLINE_INTERVAL_1DAY,
+ "3day": client.KLINE_INTERVAL_3DAY,
+ "1hour": client.KLINE_INTERVAL_1HOUR,
+ "2hour": client.KLINE_INTERVAL_2HOUR,
+ "4hour": client.KLINE_INTERVAL_4HOUR,
+ "6hour": client.KLINE_INTERVAL_6HOUR,
+ "8hour": client.KLINE_INTERVAL_8HOUR,
+ "12hour": client.KLINE_INTERVAL_12HOUR,
+ "1week": client.KLINE_INTERVAL_1WEEK,
+ "1min": client.KLINE_INTERVAL_1MINUTE,
+ "3min": client.KLINE_INTERVAL_3MINUTE,
+ "5min": client.KLINE_INTERVAL_5MINUTE,
+ "15min": client.KLINE_INTERVAL_15MINUTE,
+ "30min": client.KLINE_INTERVAL_30MINUTE,
+ "1month": client.KLINE_INTERVAL_1MONTH,
+ }
+ interval_choices = list(interval_map.keys())
+
+ parser = argparse.ArgumentParser(
+ prog="candle",
+ add_help=False,
+ description="Program to plot candles for binance data",
+ )
+ parser.add_argument(
+ "-i",
+ "--interval",
+ help="Interval to get data",
+ choices=interval_choices,
+ dest="interval",
+ default="1day",
+ type=str,
+ )
+
+ parser.add_argument(
+ "-l", "--limit", dest="limit", default=100, help="Number to get", type=int
+ )
+
+ try:
+ if not coin or not currency:
+ print("Coin needs to be selected prior to this command\n")
+ return
+
+ coin += currency
+
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return
+
+ candles = client.get_klines(
+ symbol=coin,
+ interval=interval_map[ns_parser.interval],
+ limit=ns_parser.limit,
+ )
+
+ # Response
+ # 1499040000000, // Open time
+ # "0.01634790", // Open
+ # "0.80000000", // High
+ # "0.01575800", // Low
+ # "0.01577100", // Close
+ # "148976.11427815", // Volume
+ # 1499644799999, // Close time
+ # "2434.19055334", // Quote asset volume
+ # 308, // Number of trades
+ # "1756.87402397", // Taker buy base asset volume
+ # "28.46694368", // Taker buy quote asset volume
+ # "17928899.62484339" // Ignore.
+
+ candles_df = pd.DataFrame(candles).astype(float).iloc[:, :7]
+
+ candles_df.columns = [
+ "Time0",
+ "Open",
+ "High",
+ "Low",
+ "Close",
+ "Volume",
+ "Time1",
+ ]
+ candles_df.index = pd.to_datetime(
+ (candles_df["Time0"] + candles_df["Time1"]) / 2, unit="ms"
+ )
+
+ plot_candles(
+ candles_df,
+ f"{coin} from {candles_df.index[0].strftime('%Y/%m/%d')} to "
+ f"{candles_df.index[-1].strftime('%Y/%m/%d')}",
+ )
+
+ except Exception as e:
+ print(e, "\n")
+
+
+def balance(coin: str):
+ """Get account holdings for asset
+
+ Parameters
+ ----------
+ coin: str
+ Coin to get holdings of
+ """
+ client = Client(cfg.API_BINANCE_KEY, cfg.API_BINANCE_SECRET)
+ try:
+ current_balance = client.get_asset_balance(asset=coin)
+ if current_balance is None:
+ print("Check loaded coin")
+ return
+ print("")
+ amounts = [float(current_balance["free"]), float(current_balance["locked"])]
+ total = np.sum(amounts)
+ df = pd.DataFrame(amounts).apply(lambda x: str(float(x)))
+ df.columns = ["Amount"]
+ df.index = ["Free", "Locked"]
+ df["Percent"] = df.div(df.sum(axis=0), axis=1).round(3)
+ print(f"You currently have {total} coins and the breakdown is:")
+ print(tabulate(df, headers=df.columns, showindex=True, tablefmt="fancy_grid"))
+ print("")
+ return
+ except Exception as e:
+ print(e, "\n")
+ return
diff --git a/gamestonk_terminal/cryptocurrency/binance_view.py b/gamestonk_terminal/cryptocurrency/binance_view.py
new file mode 100644
index 00000000000..8ddbebee199
--- /dev/null
+++ b/gamestonk_terminal/cryptocurrency/binance_view.py
@@ -0,0 +1,78 @@
+"""Binance view functions"""
+__docformat__ = "numpy"
+
+import numpy as np
+import matplotlib.pyplot as plt
+import mplfinance as mpf
+import pandas as pd
+from gamestonk_terminal.main_helper import plot_autoscale
+from gamestonk_terminal.config_plot import PLOT_DPI
+from gamestonk_terminal.feature_flags import USE_ION as ion
+
+
+def plot_order_book(bids: np.array, asks: np.array, coin: str):
+ """
+ Plots Bid/Ask
+ Parameters
+ ----------
+ bids : np.array
+ array of bids with columns: price, size, cumulative size
+ asks : np.array
+ array of asks with columns: price, size, cumulative size
+ coin : str
+ Coin being plotted
+
+ Returns
+ -------
+
+ """
+ _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
+ ax.plot(bids[:, 0], bids[:, 2], "g", label="bids")
+ ax.fill_between(bids[:, 0], bids[:, 2], color="g", alpha=0.4)
+ ax.plot(asks[:, 0], asks[:, 2], "r", label="asks")
+ ax.fill_between(asks[:, 0], asks[:, 2], color="r", alpha=0.4)
+ plt.grid(b=True, which="major", color="#666666", linestyle="-")
+ plt.minorticks_on()
+ plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2)
+ plt.legend(loc=0)
+ plt.xlabel("Price")
+ plt.ylabel("Size (Coins) ")
+ plt.title(f"Order Book for {coin}")
+ if ion:
+ plt.ion()
+ plt.show()
+ print("")
+
+
+def plot_candles(candles_df: pd.DataFrame, title: str):
+ """
+ Plot candle chart from dataframe
+ Parameters
+ ----------
+ candles_df: pd.DataFrame
+ Dataframe containing time and OHLVC
+ title: str
+ title of graph
+
+ Returns
+ -------
+
+ """
+ mpf.plot(
+ candles_df,
+ type="candle",
+ volume=True,
+ title=f"{title}",
+ xrotation=20,
+ style="binance",
+ figratio=(10, 7),
+ figscale=1.10,
+ figsize=(plot_autoscale()),
+ update_width_config=dict(
+ candle_linewidth=1.0, candle_width=0.8, volume_linewidth=1.0
+ ),
+ )
+ if ion:
+ plt.ion()
+ plt.show()
+ print("")
diff --git a/gamestonk_terminal/cryptocurrency/coinmarketcap_view.py b/gamestonk_terminal/cryptocurrency/coinmarketcap_view.py
index a8820889d33..981b55ec092 100644
--- a/gamestonk_terminal/cryptocurrency/coinmarketcap_view.py
+++ b/gamestonk_terminal/cryptocurrency/coinmarketcap_view.py
@@ -6,7 +6,7 @@ from typing import List
import pandas as pd
from tabulate import tabulate
from coinmarketcapapi import CoinMarketCapAPI
-from gamestonk_terminal.config_terminal import COINMARKETCAP_KEY
+import gamestonk_terminal.config_terminal as cfg
from gamestonk_terminal.helper_funcs import parse_known_args_and_warn
sort_options = ["Symbol", "CMC_Rank", "LastPrice", "DayPctChange", "MarketCap"]
@@ -62,7 +62,7 @@ def get_cmc_top_n(other_args: List[str]):
if not ns_parser:
return
- cmc = CoinMarketCapAPI(COINMARKETCAP_KEY)
+ cmc = CoinMarketCapAPI(cfg.API_CMC_KEY)
ratings = cmc.cryptocurrency_listings_latest().data
symbol, rank, price, pchange1d, mkt_cap = [], [], [], [], []
diff --git a/gamestonk_terminal/cryptocurrency/crypto_controller.py b/gamestonk_terminal/cryptocurrency/crypto_controller.py
index 0361dcbbb23..958b345f5d5 100644
--- a/gamestonk_terminal/cryptocurrency/crypto_controller.py
+++ b/gamestonk_terminal/cryptocurrency/crypto_controller.py
@@ -1,3 +1,4 @@
+"""Cryptocurrency Controller"""
__docformat__ = "numpy"
import argparse
@@ -7,13 +8,29 @@ from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import get_flair
from gamestonk_terminal.menu import session
-from gamestonk_terminal.cryptocurrency import pycoingecko_view
-from gamestonk_terminal.cryptocurrency import coinmarketcap_view as cmc_view
+from gamestonk_terminal.cryptocurrency import (
+ binance_model,
+ pycoingecko_view,
+ coinmarketcap_view as cmc_view,
+)
class CryptoController:
- CHOICES = ["help", "q", "quit", "load", "view", "top"]
+ CHOICES = [
+ "help",
+ "q",
+ "quit",
+ "load",
+ "view",
+ "top",
+ "trend",
+ "book",
+ "trades",
+ "candle",
+ "balance",
+ "select",
+ ]
def __init__(self):
"""CONSTRUCTOR"""
@@ -21,22 +38,31 @@ class CryptoController:
self.crypto_parser = argparse.ArgumentParser(add_help=False, prog="crypto")
self.crypto_parser.add_argument("cmd", choices=self.CHOICES)
self.current_coin = None
+ self.current_currency = None
self.current_df = pd.DataFrame()
- @staticmethod
- def print_help(current_coin):
+ def print_help(self):
"""Print help"""
print("\nCryptocurrency:")
print(" help show this menu again")
print(" q quit this menu, and shows back to main menu")
print(" quit quit to abandon program")
- print(f"\nCurrent Coin: {current_coin}")
+ print(f"\nCurrent Coin: {self.current_coin}")
print("")
+ print("Coingecko:")
print(" load load cryptocurrency data")
print(" view load and view cryptocurrency data")
+ print(" trend show top 7 trending coins")
print("")
+ print("CoinMarketCap:")
print(" top view top coins from coinmarketcap")
print("")
+ print("Binance:")
+ print(" select select coin/currency to use")
+ print(" book show order book")
+ print(" candle get klines/candles and plot")
+ print(" balance show coin balance")
+ print("")
def switch(self, an_input: str):
"""Process and dispatch input
@@ -56,7 +82,7 @@ class CryptoController:
def call_help(self, _):
"""Process Help command"""
- self.print_help(self.current_coin)
+ self.print_help()
def call_q(self, _):
"""Process Q command - quit the menu"""
@@ -67,9 +93,11 @@ class CryptoController:
return True
def call_load(self, other_args):
+ """Process load command"""
self.current_coin, self.current_df = pycoingecko_view.load(other_args)
def call_view(self, other_args):
+ """Process view command"""
if self.current_coin:
pycoingecko_view.view(self.current_coin, self.current_df, other_args)
@@ -77,13 +105,37 @@ class CryptoController:
print("No coin selected. Use 'load' to load the coin you want to look at.")
print("")
+ def call_trend(self, _):
+ """Process trend command"""
+ pycoingecko_view.trend()
+
def call_top(self, other_args):
+ """Process top command"""
cmc_view.get_cmc_top_n(other_args)
+ def call_book(self, other_args):
+ """Process book command"""
+ binance_model.order_book(other_args, self.current_coin, self.current_currency)
+
+ def call_candle(self, other_args):
+ """Process candle command"""
+ binance_model.show_candles(other_args, self.current_coin, self.current_currency)
+
+ def call_balance(self, _):
+ """Process balance command"""
+ binance_model.balance(self.current_coin)
+
+ def call_select(self, other_args):
+ """Process select command"""
+ self.current_coin, self.current_currency = binance_model.select_binance_coin(
+ other_args
+ )
+ print("")
+
def menu():
crypto_controller = CryptoController()
- crypto_controller.print_help(crypto_controller.current_coin)
+ crypto_controller.print_help()
plt.close("all")
while True:
# Get input command from user
diff --git a/gamestonk_terminal/cryptocurrency/crypto_helper.py b/gamestonk_terminal/cryptocurrency/crypto_helper.py
deleted file mode 100644
index f895f264bd5..00000000000
--- a/gamestonk_terminal/cryptocurrency/crypto_helper.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
-Helper script that generates a dictionary to convert between symbols and IDs.
-This allows the user to input (for example) "btc" instead of the expected "bitcoin"
-
-"""
-from pycoingecko import CoinGeckoAPI
-
-cg = CoinGeckoAPI()
-coins = cg.get_coins()
-coin_symbol_to_id = {}
-coin_ids = []
-
-for coin in coins:
- coin_symbol_to_id[coin["symbol"]] = coin["id"]
- coin_ids.append(coin["id"])
diff --git a/gamestonk_terminal/cryptocurrency/pycoingecko_view.py b/gamestonk_terminal/cryptocurrency/pycoingecko_view.py
index 3dd11a7663f..c4f6fca5f3c 100644
--- a/gamestonk_terminal/cryptocurrency/pycoingecko_view.py
+++ b/gamestonk_terminal/cryptocurrency/pycoingecko_view.py
@@ -6,12 +6,23 @@ from typing import List
import pandas as pd
from pandas.plotting import register_matplotlib_converters
import matplotlib.pyplot as plt
+from tabulate import tabulate
from pycoingecko import CoinGeckoAPI
from gamestonk_terminal.helper_funcs import parse_known_args_and_warn, plot_autoscale
-from gamestonk_terminal.cryptocurrency.crypto_helper import coin_symbol_to_id, coin_ids
from gamestonk_terminal.config_plot import PLOT_DPI
register_matplotlib_converters()
+
+# Generate a list of valid coins to be checked against later
+cg_api = CoinGeckoAPI()
+coins = cg_api.get_coins()
+coin_symbol_to_id = {}
+coin_ids = []
+
+for single_coin in coins:
+ coin_symbol_to_id[single_coin["symbol"]] = single_coin["id"]
+ coin_ids.append(single_coin["id"])
+
# pylint: disable=inconsistent-return-statements
@@ -135,3 +146,33 @@ def view(coin: str, prices: pd.DataFrame, other_args: List[str]):
except Exception as e:
print(e)
print("")
+
+
+def trend():
+ """Prints top 7 coins from pycoingecko"""
+
+ cg = CoinGeckoAPI()
+ trending = cg.get_search_trending()["coins"]
+ name, symbol, price, rank = [], [], [], []
+ for coin in trending:
+ name.append(coin["item"]["name"])
+ symbol.append(coin["item"]["symbol"])
+ coin_id = coin["item"]["id"]
+ price.append(cg.get_price(coin_id, vs_currencies="USD")[coin_id]["usd"])
+ rank.append(coin["item"]["market_cap_rank"])
+
+ df = pd.DataFrame()
+ df["name"] = name
+ df["symbol"] = symbol
+ df["last_price"] = price
+ df["market_cap_rank"] = rank
+ print(
+ tabulate(
+ df,
+ headers=df.columns,
+ floatfmt=".2f",
+ showindex=False,
+ tablefmt="fancy_grid",
+ )
+ )
+ print("")
diff --git a/gamestonk_terminal/discovery/README.md b/gamestonk_terminal/discovery/README.md
index 1159e591b56..b7b3ac0200a 100644
--- a/gamestonk_terminal/discovery/README.md
+++ b/gamestonk_terminal/discovery/README.md
@@ -2,6 +2,8 @@
This menu aims to discover new stocks, and the usage of the following commands along with an example will be exploited below.
+* [ipo](#ipo)
+ * past and future IPOs [Finnhub]
* [map](#map)
* S&P500 index stocks map [Finviz]
* [rtp_sectors](#rtp_sectors)
@@ -42,6 +44,20 @@ This menu aims to discover new stocks, and the usage of the following commands a
* dark pool tickers with growing activity [FINRA]
+## ipo
+
+```shell
+usage: ipo [-p PAST_DAYS] [-f FUTURE_DAYS]
+```
+
+Past and future IPOs. [Source: https://finnhub.io]
+
+* -p : Number of past days to look for IPOs. Default 0.
+* -f : Number of future days to look for IPOs. Default 10.
+
+
+
+
## map
```shell
diff --git a/gamestonk_terminal/discovery/disc_controller.py b/gamestonk_terminal/discovery/disc_controller.py
index 855f2eeb0d4..07731915805 100644
--- a/gamestonk_terminal/discovery/disc_controller.py
+++ b/gamestonk_terminal/discovery/disc_controller.py
@@ -22,6 +22,7 @@ from gamestonk_terminal.discovery import (
yahoo_finance_view,
marketbeat_view,
finra_ats_view,
+ finnhub_view,
)
@@ -33,6 +34,7 @@ class DiscoveryController:
"help",
"q",
"quit",
+ "ipo",
"map",
"rtp_sectors",
"gainers",
@@ -72,6 +74,7 @@ class DiscoveryController:
print(" q quit this menu, and shows back to main menu")
print(" quit quit to abandon program")
print("")
+ print(" ipo past and future IPOs [Finnhub]")
print(" map S&P500 index stocks map [Finviz]")
print(" rtp_sectors real-time performance sectors [Alpha Vantage]")
print(" gainers show latest top gainers [Yahoo Finance]")
@@ -135,6 +138,10 @@ class DiscoveryController:
"""Process Quit command - quit the program"""
return True
+ def call_ipo(self, other_args: List[str]):
+ """Process ipo command"""
+ finnhub_view.ipo_calendar(other_args)
+
def call_map(self, other_args: List[str]):
"""Process map command"""
finviz_view.map_sp500_view(other_args)
diff --git a/gamestonk_terminal/discovery/finnhub_view.py b/gamestonk_terminal/discovery/finnhub_view.py
new file mode 100644
index 00000000000..8fa058b54c3
--- /dev/null
+++ b/gamestonk_terminal/discovery/finnhub_view.py
@@ -0,0 +1,122 @@
+import argparse
+from typing import List
+from datetime import datetime, timedelta
+import requests
+import pandas as pd
+from gamestonk_terminal import config_terminal as cfg
+from gamestonk_terminal.helper_funcs import (
+ parse_known_args_and_warn,
+ check_non_negative,
+)
+
+
+def get_ipo_calendar(from_date: str, to_date: str) -> pd.DataFrame:
+ """Get IPO calendar
+
+ Parameters
+ ----------
+ from_date : str
+ from date (%Y-%m-%d) to get IPO calendar
+ to_date : str
+ to date (%Y-%m-%d) to get IPO calendar
+
+ Returns
+ -------
+ pd.DataFrame
+ Get dataframe with economic calendar events
+ """
+ response = requests.get(
+ f"https://finnhub.io/api/v1/calendar/ipo?from={from_date}&to={to_date}&token={cfg.API_FINNHUB_KEY}"
+ )
+ if response.status_code == 200:
+ d_data = response.json()
+ if "ipoCalendar" in d_data:
+ d_refactor_columns = {
+ "numberOfShares": "Number of Shares",
+ "totalSharesValue": "Total Shares Value",
+ "date": "Date",
+ "exchange": "Exchange",
+ "name": "Name",
+ "price": "Price",
+ "status": "Status",
+ }
+ return pd.DataFrame(d_data["ipoCalendar"]).rename(
+ columns=d_refactor_columns
+ )
+
+ return pd.DataFrame()
+
+
+def ipo_calendar(other_args: List[str]):
+ """Past and future IPOs calendar [Finnhub]
+
+ Parameters
+ ----------
+ other_args : List[str]
+ Command line arguments to be processed with argparse
+ """
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ prog="ipo",
+ description="""
+ Past and future IPOs. [Source: https://finnhub.io]
+ """,
+ )
+ parser.add_argument(
+ "-p",
+ "--past",
+ action="store",
+ dest="past_days",
+ type=check_non_negative,
+ default=0,
+ help="Number of past days to look for IPOs.",
+ )
+ parser.add_argument(
+ "-f",
+ "--future",
+ action="store",
+ dest="future_days",
+ type=check_non_negative,
+ default=10,
+ help="Number of future days to look for IPOs.",
+ )
+
+ try:
+ if other_args:
+ if "-" not in other_args[0]:
+ other_args.insert(0, "-f")
+
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return
+
+ today = datetime.now()
+
+ if ns_parser.future_days > 0:
+ future_date = today + timedelta(days=ns_parser.future_days)
+
+ df_future_ipo = get_ipo_calendar(
+ today.strftime("%Y-%m-%d"), future_date.strftime("%Y-%m-%d")
+ ).rename(columns={"Date": "Future"})
+
+ if df_future_ipo.empty:
+ print(f"No IPOs found for the next {ns_parser.future_days} days")
+ else:
+ print(df_future_ipo.to_string(index=False))
+ print("")
+
+ if ns_parser.past_days > 0:
+ past_date = today - timedelta(days=ns_parser.past_days)
+
+ df_past_ipo = get_ipo_calendar(
+ past_date.strftime("%Y-%m-%d"), today.strftime("%Y-%m-%d")
+ ).rename(columns={"Date": "Past"})
+
+ if df_past_ipo.empty:
+ print(f"No IPOs found since the last {ns_parser.past_days} days")
+ else:
+ print(df_past_ipo.to_string(index=False))
+ print("")
+
+ except Exception as e:
+ print(e, "\n")
diff --git a/gamestonk_terminal/due_diligence/README.md b/gamestonk_terminal/due_diligence/README.md
index cb8854b78e5..7a88574392a 100644
--- a/gamestonk_terminal/due_diligence/README.md
+++ b/gamestonk_terminal/due_diligence/README.md
@@ -12,6 +12,8 @@ This menu aims to help in due-diligence of a pre-loaded stock, and the usage of
* rating of the company from strong sell to strong buy [FMP]
* [pt](#pt)
* price targets over time [Business Insider]
+* [rot](#rot)
+ * ratings over time [Finnhub]
* [est](#est)
* quarter and year analysts earnings estimates [Business Insider]
* [ins](#ins)
@@ -89,6 +91,18 @@ Prints price target from analysts. [Source: Business Insider]
+
+## rot
+
+```text
+usage: rot
+```
+
+Plots ratings over time. [Source: Finnhub]
+
+
+
+
## est
```text
diff --git a/gamestonk_terminal/due_diligence/dd_controller.py b/gamestonk_terminal/due_diligence/dd_controller.py
index 27efe9eb880..f24ddcad017 100644
--- a/gamestonk_terminal/due_diligence/dd_controller.py
+++ b/gamestonk_terminal/due_diligence/dd_controller.py
@@ -15,6 +15,7 @@ from gamestonk_terminal.due_diligence import reddit_view as r_view
from gamestonk_terminal.due_diligence import news_view
from gamestonk_terminal.due_diligence import finra_view
from gamestonk_terminal.due_diligence import sec_view
+from gamestonk_terminal.due_diligence import finnhub_view
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import get_flair
from gamestonk_terminal.menu import session
@@ -32,6 +33,7 @@ class DueDiligenceController:
"short",
"rating",
"pt",
+ "rot",
"est",
"ins",
"insider",
@@ -91,6 +93,9 @@ class DueDiligenceController:
" rating rating of the company from strong sell to strong buy [FMP]"
)
print(" pt price targets over time [Business Insider]")
+ print(
+ " rot rating over timefrom strong sell to strong buy [Finnhub]"
+ )
print(
" est quarter and year analysts earnings estimates [Business Insider]"
)
@@ -155,6 +160,10 @@ class DueDiligenceController:
other_args, self.stock, self.ticker, self.start, self.interval
)
+ def call_rot(self, other_args: List[str]):
+ """Process rot command"""
+ finnhub_view.rating_over_time(other_args, self.ticker)
+
def call_est(self, other_args: List[str]):
"""Process est command"""
bi_view.estimates(other_args, self.ticker)
diff --git a/gamestonk_terminal/due_diligence/finnhub_view.py b/gamestonk_terminal/due_diligence/finnhub_view.py
new file mode 100644
index 00000000000..112032ea462
--- /dev/null
+++ b/gamestonk_terminal/due_diligence/finnhub_view.py
@@ -0,0 +1,108 @@
+import argparse
+from typing import List
+import requests
+import pandas as pd
+from matplotlib import pyplot as plt
+from pandas.plotting import register_matplotlib_converters
+from gamestonk_terminal import config_terminal as cfg
+from gamestonk_terminal.helper_funcs import (
+ parse_known_args_and_warn,
+ plot_autoscale,
+)
+from gamestonk_terminal.config_plot import PLOT_DPI
+from gamestonk_terminal import feature_flags as gtff
+
+register_matplotlib_converters()
+
+
+def get_rating_over_time(ticker: str) -> pd.DataFrame:
+ """Get rating over time
+
+ Parameters
+ ----------
+ ticker : str
+ Ticker to get ratings from
+
+ Returns
+ -------
+ pd.DataFrame
+ Get dataframe with economic calendar events
+ """
+ response = requests.get(
+ f"https://finnhub.io/api/v1/stock/recommendation?symbol={ticker}&token={cfg.API_FINNHUB_KEY}"
+ )
+ if response.status_code == 200:
+ return pd.DataFrame(response.json())
+
+ return pd.DataFrame()
+
+
+def plot_rating_over_time(rot: pd.DataFrame, ticker: str):
+ """Plot rating over time
+
+ Parameters
+ ----------
+ rot : pd.DataFrame
+ Rating over time
+ ticker : str
+ Ticker associated with ratings
+
+ """
+ plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI)
+
+ rot.sort_values("period", inplace=True)
+ plt.plot(pd.to_datetime(rot["period"]), rot["strongBuy"], c="green", lw=3)
+ plt.plot(pd.to_datetime(rot["period"]), rot["buy"], c="lightgreen", lw=3)
+ plt.plot(pd.to_datetime(rot["period"]), rot["hold"], c="grey", lw=3)
+ plt.plot(pd.to_datetime(rot["period"]), rot["sell"], c="pink", lw=3)
+ plt.plot(pd.to_datetime(rot["period"]), rot["strongSell"], c="red", lw=3)
+ plt.xlim(
+ pd.to_datetime(rot["period"].values[0]),
+ pd.to_datetime(rot["period"].values[-1]),
+ )
+ plt.grid()
+ plt.title(f"{ticker}'s ratings over time")
+ plt.xlabel("Time")
+ plt.ylabel("Rating")
+ plt.legend(["Strong Buy", "Buy", "Hold", "Sell", "Strong Sell"])
+
+ if gtff.USE_ION:
+ plt.ion()
+
+ plt.show()
+
+
+def rating_over_time(other_args: List[str], ticker: str):
+ """Rating over time
+
+ Parameters
+ ----------
+ other_args : List[str]
+ Command line arguments to be processed with argparse
+ ticker : str
+ Ticker to get ratings from
+ """
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ prog="rot",
+ description="""
+ Rating over time. [Source: https://finnhub.io]
+ """,
+ )
+
+ try:
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return
+
+ df_rot = get_rating_over_time(ticker)
+
+ if df_rot.empty:
+ print("No ratings over time found\n")
+ return
+
+ plot_rating_over_time(df_rot, ticker)
+ print("")
+
+ except Exception as e:
+ print(e, "\n")
diff --git a/gamestonk_terminal/econ/README.md b/gamestonk_terminal/econ/README.md
index 1e6bb26f4b9..8bd5054abed 100644
--- a/gamestonk_terminal/econ/README.md
+++ b/gamestonk_terminal/econ/README.md
@@ -1,7 +1,9 @@
-# ECONONMY
+# ECONOMY
This menu aims to assess economy data, and the usage of the following commands along with an example will be exploited below.
+* [events](#events)
+ * economic impact events [Finnhub]
* [fred](#fred)
* display customized FRED from https://fred.stlouisfed.org
* [vixcls](#vixcls)
@@ -28,6 +30,20 @@ This menu aims to assess economy data, and the usage of the following commands a
* Canada / U.S. Foreign Exchange Rate (CAD per 1 USD)
+## events
+```text
+usage: events [-c {NZ,AU,ERL,CA,EU,US,JP,CN,GB,CH}] [-i {low,medium,high,all}] [-n NUM]
+```
+
+Output economy impact calendar impact events. [Source: https://finnhub.io]
+
+* -c : Country from where to get economy calendar impact events, between NZ,AU,ERL,CA,EU,US,JP,CN,GB,CH. Default: US.
+* -i : Impact of the economy event, between low,medium,high,all. Default: all.
+* -n : Number economy calendar impact events to display. Default: 10.
+
+
+
+
## fred
```text
usage: fred [-i SERIES_ID] [-s START_DATE] [-t]
diff --git a/gamestonk_terminal/econ/econ_controller.py b/gamestonk_terminal/econ/econ_controller.py
index 2bdaf1e8727..1b47f16d6b1 100644
--- a/gamestonk_terminal/econ/econ_controller.py
+++ b/gamestonk_terminal/econ/econ_controller.py
@@ -9,6 +9,7 @@ from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import get_flair
from gamestonk_terminal.menu import session
from gamestonk_terminal.econ import fred_view
+from gamestonk_terminal.econ import finnhub_view
class EconController:
@@ -19,6 +20,7 @@ class EconController:
"help",
"q",
"quit",
+ "events",
"fred",
"vixcls",
"gdp",
@@ -50,6 +52,7 @@ class EconController:
print(" q quit this menu, and shows back to main menu")
print(" quit quit to abandon program")
print(" ")
+ print(" events economic impact events [Finnhub]")
print(
" fred display customized FRED data from https://fred.stlouisfed.org"
)
@@ -95,6 +98,10 @@ class EconController:
"""Process Quit command - quit the program"""
return True
+ def call_events(self, other_args: List[str]):
+ """Process events command"""
+ finnhub_view.economy_calendar_events(other_args)
+
def call_fred(self, other_args: List[str]):
"""Process fred command"""
fred_view.display_fred(other_args, "")
diff --git a/gamestonk_terminal/econ/finnhub_view.py b/gamestonk_terminal/econ/finnhub_view.py
new file mode 100644
index 00000000000..2f4d45d6c32
--- /dev/null
+++ b/gamestonk_terminal/econ/finnhub_view.py
@@ -0,0 +1,131 @@
+import argparse
+from typing import List
+import requests
+import pandas as pd
+from gamestonk_terminal import config_terminal as cfg
+from gamestonk_terminal.helper_funcs import (
+ parse_known_args_and_warn,
+ check_positive,
+)
+
+
+def get_economy_calendar_events() -> pd.DataFrame:
+ """Get economic calendar events
+
+ Returns
+ -------
+ pd.DataFrame
+ Get dataframe with economic calendar events
+ """
+ response = requests.get(
+ f"https://finnhub.io/api/v1/calendar/economic?token={cfg.API_FINNHUB_KEY}"
+ )
+ if response.status_code == 200:
+ d_data = response.json()
+ if "economicCalendar" in d_data:
+ return pd.DataFrame(d_data["economicCalendar"])
+
+ return pd.DataFrame()
+
+
+def economy_calendar_events(other_args: List[str]):
+ """Output economy calendar impact events
+
+ Parameters
+ ----------
+ other_args : List[str]
+ Command line arguments to be processed with argparse
+ """
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ prog="events",
+ description="""
+ Output economy impact calendar impact events. [Source: https://finnhub.io]
+ """,
+ )
+ parser.add_argument(
+ "-c",
+ "--country",
+ action="store",
+ dest="country",
+ type=str,
+ default="US",
+ choices=["NZ", "AU", "ERL", "CA", "EU", "US", "JP", "CN", "GB", "CH"],
+ help="Country from where to get economy calendar impact events",
+ )
+ parser.add_argument(
+ "-n",
+ "--num",
+ action="store",
+ dest="num",
+ type=check_positive,
+ default=10,
+ help="Number economy calendar impact events to display",
+ )
+ parser.add_argument(
+ "-i",
+ "--impact",
+ action="store",
+ dest="impact",
+ type=str,
+ default="all",
+ choices=["low", "medium", "high", "all"],
+ help="Impact of the economy event",
+ )
+
+ try:
+ if other_args:
+ if "-" not in other_args[0]:
+ other_args.insert(0, "-c")
+
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return
+
+ df_events = get_economy_calendar_events()
+
+ if df_events.empty:
+ print("No latest economy calendar events found\n")
+ return
+
+ df_econ_calendar = df_events[
+ df_events["country"] == ns_parser.country
+ ].sort_values("time", ascending=False)
+
+ if df_econ_calendar.empty:
+ print("No latet economy calendar events found in the specified country\n")
+ return
+
+ if ns_parser.impact != "all":
+ df_econ_calendar = df_econ_calendar[
+ df_econ_calendar["impact"] == ns_parser.impact
+ ]
+
+ if df_econ_calendar.empty:
+ print(
+ "No latet economy calendar events found in the specified country with this impact\n"
+ )
+ return
+
+ df_econ_calendar = df_econ_calendar.fillna("---").head(n=ns_parser.num)
+
+ d_econ_calendar_map = {
+ "actual": "Actual release",
+ "prev": "Previous release",
+ "country": "Country",
+ "unit": "Unit",
+ "estimate": "Estimate",
+ "event": "Event",
+ "impact": "Impact Level",
+ "time": "Release time",
+ }
+
+ df_econ_calendar = df_econ_calendar[
+ ["time", "event", "impact", "prev", "estimate", "actual", "unit"]
+ ].rename(columns=d_econ_calendar_map)
+
+ print(df_econ_calendar.to_string(index=False))
+ print("")
+
+ except Exception as e:
+ print(e, "\n")
diff --git a/gamestonk_terminal/econ/fred_view.py b/gamestonk_terminal/econ/fred_view.py
index 59ccd2c2682..88ba23b42e8 100644
--- a/gamestonk_terminal/econ/fred_view.py
+++ b/gamestonk_terminal/econ/fred_view.py
@@ -58,7 +58,7 @@ def display_fred(other_args: List[str], choice: str):
"-i",
"--id",
dest="series_id",
- required=bool(choice),
+ required=not bool(choice),
type=str,
default=choice.upper(),
help="FRED Series ID from https://fred.stlouisfed.org",
diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py
index 824e14918b2..30ef54f0af0 100644
--- a/gamestonk_terminal/main_helper.py
+++ b/gamestonk_terminal/main_helper.py
@@ -55,11 +55,13 @@ def print_help(s_ticker, s_start, s_interval, b_is_market_open):
" > scr screener stocks, \t\t e.g. overview/performance, using preset filters"
)
print(" > mill papermill menu, \t\t menu to generate notebook reports")
- print(" > econ economic data, \t\t from: FRED, VIX")
+ print(" > econ economic data, \t\t e.g.: FRED, events")
print(
" > pa portfolio analysis, \t\t supports: robinhood, alpaca, ally "
)
- print(" > crypto cryptocurrencies, \t\t uses coingecko api")
+ print(
+ " > crypto cryptocurrencies, \t\t from: coingecko, coinmarketcap, binance"
+ )
print(
" > po portfolio optimization, \t optimal portfolio weights from pyportfolioopt"
)
diff --git a/gamestonk_terminal/technical_analysis/README.md b/gamestonk_terminal/technical_analysis/README.md
index 0e5c511d850..aa5ca8a0fe6 100644
--- a/gamestonk_terminal/technical_analysis/README.md
+++ b/gamestonk_terminal/technical_analysis/README.md
@@ -8,6 +8,8 @@ This menu aims to perform a technical analysis on a pre-loaded ticker chart, and
- technical summary report [FinBrain API]
* [recom](#recom)
- recommendation based on Technical Indicators [Tradingview API]
+ * [pr](#pr)
+ - pattern recognition [Finnhub]
[OVERLAP](#OVERLAP)
* [ema](#ema)
@@ -79,6 +81,19 @@ Print tradingview recommendation based on technical indicators. [Source: https:/
+## pr
+```
+usage: pr [-r {1,5,15,30,60,D,W,M}]
+```
+Display pattern recognition signals on the data. [Source: https://finnhub.io]
+
+* -r : Plot resolution to look for pattern signals. Default 'D'.
+
+
+
+
+
+
# OVERLAP
## ema
diff --git a/gamestonk_terminal/technical_analysis/finnhub_view.py b/gamestonk_terminal/technical_analysis/finnhub_view.py
new file mode 100644
index 00000000000..67db2b88951
--- /dev/null
+++ b/gamestonk_terminal/technical_analysis/finnhub_view.py
@@ -0,0 +1,185 @@
+import argparse
+from typing import List
+import math
+from datetime import datetime
+import requests
+import yfinance as yf
+import mplfinance as mpf
+import pandas as pd
+from gamestonk_terminal import config_terminal as cfg
+from gamestonk_terminal.helper_funcs import (
+ parse_known_args_and_warn,
+ plot_autoscale,
+)
+
+
+def get_pattern_recognition(ticker: str, resolution: str) -> pd.DataFrame:
+ """Get pattern recognition data
+
+ Parameters
+ ----------
+ ticker : str
+ Ticker to get pattern recognition data
+ resolution : str
+ Resolution of data to get pattern recognition from
+
+ Returns
+ -------
+ pd.DataFrame
+ Get datapoints corresponding to pattern signal data
+ """
+ response = requests.get(
+ f"https://finnhub.io/api/v1/scan/pattern?symbol={ticker}&resolution={resolution}&token={cfg.API_FINNHUB_KEY}"
+ )
+ if response.status_code == 200:
+ d_data = response.json()
+ if "points" in d_data:
+ return pd.DataFrame(d_data["points"]).T
+
+ return pd.DataFrame()
+
+
+def plot_pattern_recognition(ticker: str, pattern: pd.DataFrame):
+ """Plot pattern recognition signal
+
+ Parameters
+ ----------
+ ticker : str
+ Ticker to display pattern recognition on top of the data
+ pattern : pd.DataFrame
+ Pattern recognition signal data
+ """
+
+ l_segments = list()
+ for i in pattern:
+ a_part = ("", "")
+ if "aprice" in pattern[i]:
+ if pattern[i]["aprice"] != 0 and not math.isnan(pattern[i]["aprice"]):
+ a_part = (
+ datetime.utcfromtimestamp(pattern[i]["atime"]).strftime("%Y-%m-%d"),
+ pattern[i]["aprice"],
+ )
+
+ b_part = ("", "")
+ if "bprice" in pattern[i]:
+ if pattern[i]["bprice"] != 0 and not math.isnan(pattern[i]["bprice"]):
+ b_part = (
+ datetime.utcfromtimestamp(pattern[i]["btime"]).strftime("%Y-%m-%d"),
+ pattern[i]["bprice"],
+ )
+
+ c_part = ("", "")
+ if "cprice" in pattern[i]:
+ if pattern[i]["cprice"] != 0 and not math.isnan(pattern[i]["cprice"]):
+ c_part = (
+ datetime.utcfromtimestamp(pattern[i]["ctime"]).strftime("%Y-%m-%d"),
+ pattern[i]["cprice"],
+ )
+
+ d_part = ("", "")
+ if "dprice" in pattern[i]:
+ if pattern[i]["dprice"] != 0 and not math.isnan(pattern[i]["dprice"]):
+ d_part = (
+ datetime.utcfromtimestamp(pattern[i]["dtime"]).strftime("%Y-%m-%d"),
+ pattern[i]["dprice"],
+ )
+
+ segment = (a_part, b_part, c_part, d_part)
+
+ l_segment = list(segment)
+ while ("", "") in l_segment:
+ l_segment.remove(("", ""))
+ segm = tuple(l_segment)
+
+ l_segments.append(segm)
+
+ start_time = 999999999999
+ for i in pattern:
+ if pattern[i]["atime"] < start_time:
+ start_time = pattern[i]["atime"]
+
+ df_stock = yf.download(
+ ticker,
+ start=datetime.utcfromtimestamp(start_time).strftime("%Y-%m-%d"),
+ progress=False,
+ )
+
+ df_stock["date_id"] = (df_stock.index.date - df_stock.index.date.min()).astype(
+ "timedelta64[D]"
+ )
+ df_stock["date_id"] = df_stock["date_id"].dt.days + 1
+
+ df_stock["OC_High"] = df_stock[["Open", "Close"]].max(axis=1)
+ df_stock["OC_Low"] = df_stock[["Open", "Close"]].min(axis=1)
+
+ mc = mpf.make_marketcolors(
+ up="green", down="red", edge="black", wick="black", volume="in", ohlc="i"
+ )
+
+ s = mpf.make_mpf_style(marketcolors=mc, gridstyle=":", y_on_right=False)
+
+ mpf.plot(
+ df_stock,
+ type="candle",
+ volume=False,
+ title=f"\n{ticker}",
+ alines=l_segments,
+ xrotation=10,
+ style=s,
+ figratio=(10, 7),
+ figscale=1.10,
+ figsize=plot_autoscale(),
+ update_width_config=dict(
+ candle_linewidth=1.0, candle_width=0.8, volume_linewidth=1.0
+ ),
+ )
+
+ for ix in range(len(pattern.columns)):
+ print(f"From {l_segments[ix][0][0]} to {l_segments[ix][-1][0]}")
+ print(f"Pattern: {pattern[0]['patternname']} ({pattern[0]['patterntype']})")
+ print("")
+
+
+def pattern_recognition_view(other_args: List[str], ticker: str):
+ """Display pattern recognition signals on the data
+
+ Parameters
+ ----------
+ other_args : List[str]
+ Command line arguments to be processed with argparse
+ ticker : str
+ Ticker to display pattern recognition on top of the data
+ """
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ prog="pr",
+ description="""
+ Display pattern recognition signals on the data. [Source: https://finnhub.io]
+ """,
+ )
+ parser.add_argument(
+ "-r",
+ "--resolution",
+ action="store",
+ dest="resolution",
+ type=str,
+ default="D",
+ choices=["1", "5", "15", "30", "60", "D", "W", "M"],
+ help="Plot resolution to look for pattern signals",
+ )
+
+ try:
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return
+
+ df_pattern = get_pattern_recognition(ticker, ns_parser.resolution)
+
+ if df_pattern.empty:
+ print("No pattern identified in this data", "\n")
+ return
+
+ plot_pattern_recognition(ticker, df_pattern)
+
+ except Exception as e:
+ print(e, "\n")
diff --git a/gamestonk_terminal/technical_analysis/ta_controller.py b/gamestonk_terminal/technical_analysis/ta_controller.py
index 620e13ab55f..2d48fced7d9 100644
--- a/gamestonk_terminal/technical_analysis/ta_controller.py
+++ b/gamestonk_terminal/technical_analysis/ta_controller.py
@@ -20,6 +20,7 @@ from gamestonk_terminal.technical_analysis import volume as ta_volume
from gamestonk_terminal.technical_analysis import finbrain_view
from gamestonk_terminal.technical_analysis import tradingview_view
from gamestonk_terminal.technical_analysis import finviz_view
+from gamestonk_terminal.technical_analysis import finnhub_view
class TechnicalAnalysisController:
@@ -33,6 +34,7 @@ class TechnicalAnalysisController:
"view",
"summary",
"recom",
+ "pr",
"ema",
"sma",
"vwap",
@@ -88,6 +90,8 @@ class TechnicalAnalysisController:
print(
" recom recommendation based on Technical Indicators [Tradingview API]"
)
+ print(" pr pattern recognition [Finnhub]")
+ print("")
print("overlap:")
print(" ema exponential moving average")
print(" sma simple moving average")
@@ -157,6 +161,10 @@ class TechnicalAnalysisController:
"""Process recom command"""
tradingview_view.print_recommendation(other_args, self.ticker)
+ def call_pr(self, other_args: List[str]):
+ """Process pr command"""
+ finnhub_view.pattern_recognition_view(other_args, self.ticker)
+
# OVERLAP
def call_ema(self, other_args: List[str]):
"""Process ema command"""
diff --git a/images/gst_logo_lockup_rGreen_with_letters.png b/images/gst_logo_lockup_rGreen_with_letters.png
new file mode 100644
index 00000000000..a3e6d6782f6
Binary files /dev/null and b/images/gst_logo_lockup_rGreen_with_letters.png differ
diff --git a/poetry.lock b/poetry.lock
index 3b481c673f8..6b62906f552 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -436,6 +436,23 @@ category = "main"
optional = false
python-versions = ">=3.6, <3.7"
+[[package]]
+name = "dateparser"
+version = "1.0.0"
+description = "Date parsing library designed to parse dates from HTML pages"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[package.dependencies]
+python-dateutil = "*"
+pytz = "*"
+regex = "!=2019.02.19"
+tzlocal = "*"
+
+[package.extras]
+calendars = ["convertdate", "hijri-converter", "convertdate"]
+
[[package]]
name = "datetime"
version = "4.3"
@@ -712,7 +729,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "gdown"
-version = "3.12.2"
+version = "3.13.0"
description = "Google Drive direct download of big files."
category = "main"
optional = true
@@ -720,7 +737,7 @@ python-versions = "*"
[package.dependencies]
filelock = "*"
-requests = {version = "*", extras = ["socks"]}
+requests = {version = ">=2.12.0", extras = ["socks"]}
six = "*"
tqdm = "*"
@@ -2009,7 +2026,7 @@ python-versions = "*"
[[package]]
name = "pyobjc-core"
-version = "7.1"
+version = "7.2"
description = "Python<->ObjC Interoperability Module"
category = "main"
optional = false
@@ -2017,14 +2034,14 @@ python-versions = ">=3.6"
[[package]]
name = "pyobjc-framework-cocoa"
-version = "7.1"
+version = "7.2"
description = "Wrappers for the Cocoa frameworks on macOS"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
-pyobjc-core = ">=7.1"
+pyobjc-core = ">=7.2"
[[package]]
name = "pyotp"
@@ -2120,6 +2137,22 @@ toml = "*"
[package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
+[[package]]
+name = "python-binance"
+version = "1.0.1"
+description = "Binance REST API python implementation"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+aiohttp = "*"
+dateparser = "*"
+requests = "*"
+six = "*"
+ujson = "*"
+websockets = "*"
+
[[package]]
name = "python-coinmarketcap"
version = "0.2"
@@ -2173,7 +2206,7 @@ python-versions = "*"
[[package]]
name = "pyupgrade"
-version = "2.13.0"
+version = "2.14.0"
description = "A tool to automatically upgrade syntax for newer versions."
category = "dev"
optional = false
@@ -2232,7 +2265,7 @@ scipy = ">=0.13.2"
[[package]]
name = "qtconsole"
-version = "5.0.3"
+version = "5.1.0"
description = "Jupyter Qt console"
category = "main"
optional = false
@@ -2863,6 +2896,25 @@ category = "main"
optional = false
python-versions = "*"
+[[package]]
+name = "tzlocal"
+version = "2.1"
+description = "tzinfo object for the local timezone"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pytz = "*"
+
+[[package]]
+name = "ujson"
+version = "4.0.2"
+description = "Ultra fast JSON encoder and decoder for Python"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
[[package]]
name = "update-checker"
version = "0.18.0"
@@ -3036,7 +3088,7 @@ prediction = ["fbprophet", "tensorflow", "pmdarima", "transformers", "flair", "t
[metadata]
lock-version = "1.1"
python-versions = "^3.6.8"
-content-hash = "51e9191a579c58d229a6a32b419f3f8d891d2e7ce12decb6f74a283fb46a851a"
+content-hash = "9f2c8b046494863b5a83e395b8a79edbca42fd2b61b5e977877e08aedc53e165"
[metadata.files]
absl-py = [
@@ -3329,6 +3381,10 @@ dataclasses = [
{file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"},
{file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"},
]
+dateparser = [
+ {file = "dateparser-1.0.0-py2.py3-none-any.whl", hash = "sha256:17202df32c7a36e773136ff353aa3767e987f8b3e27374c39fd21a30a803d6f8"},
+ {file = "dateparser-1.0.0.tar.gz", hash = "sha256:159cc4e01a593706a15cd4e269a0b3345edf3aef8bf9278a57dac8adf5bf1e4a"},
+]
datetime = [
{file = "DateTime-4.3-py2.py3-none-any.whl", hash = "sha256:371dba07417b929a4fa685c2f7a3eaa6a62d60c02947831f97d4df9a9e70dfd0"},
{file = "DateTime-4.3.tar.gz", hash = "sha256:5cef605bab8259ff61281762cdf3290e459fbf0b4719951d5fab967d5f2ea0ea"},
@@ -3458,7 +3514,7 @@ gast = [
{file = "gast-0.3.3.tar.gz", hash = "sha256:b881ef288a49aa81440d2c5eb8aeefd4c2bb8993d5f50edae7413a85bfdb3b57"},
]
gdown = [
- {file = "gdown-3.12.2.tar.gz", hash = "sha256:4b3a1301e57bfd8dce939bf25ef8fbb4b23967fd0f878eede328bdcc41386bac"},
+ {file = "gdown-3.13.0.tar.gz", hash = "sha256:d5f9389539673875712beba4936c4ace95d24324953c6f0408a858c534c0bf21"},
]
gensim = [
{file = "gensim-3.8.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:61eed1d6b5fbe6dda0586ea447ebc2dc7890a7f70c2ed953d5abc3fe3cfb94bb"},
@@ -4335,20 +4391,20 @@ pymeeus = [
{file = "PyMeeus-0.5.11.tar.gz", hash = "sha256:bb9d670818d8b0594317b48a7dadea02a0594e5344263bf2054e1a011c8fed55"},
]
pyobjc-core = [
- {file = "pyobjc-core-7.1.tar.gz", hash = "sha256:a0616d5d816b4471f8f782c3a9a8923d2cc85014d88ad4f7fec694be9e6ea349"},
- {file = "pyobjc_core-7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9fb45c9916f2a03ecd6b9ecde4c35d1d0f1a590ae2ea2372f9d9a360226ac1d"},
- {file = "pyobjc_core-7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fff8e87358c6195a2937004f279050cce3d4c02cd77acd73c5ad367307def855"},
- {file = "pyobjc_core-7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:afb38efd3f2960eb49eb78552d465cfd025a9d6efa06cd4cd8694dafbe7c6e06"},
- {file = "pyobjc_core-7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7cb329c4119044fe83bcb3c5d4794d636c706ff0cb7c1c77d36ef5c373100082"},
- {file = "pyobjc_core-7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7913d7b20217c294900537faf58e5cc15942ed7af277bf05db25667d18255114"},
+ {file = "pyobjc-core-7.2.tar.gz", hash = "sha256:9e9ec482d80ea030cdb1613d05a247f31eedabe6666d884d42dd890cc5fb0e05"},
+ {file = "pyobjc_core-7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:94b4d9de9d228db52dd35012096d63bdf8c1ace58ea3be1d5f6f39313cd502f2"},
+ {file = "pyobjc_core-7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:971cbd7189ae1aa03ef0d16124aa5bcd053779e0e6b6011a41c3dbd5b4ea7e88"},
+ {file = "pyobjc_core-7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9d93b20394008373d6d2856d49aaff26f4b97ff42d924a14516c8a82313ec8c0"},
+ {file = "pyobjc_core-7.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:860183540d1be792c26426018139ac8ba75e85f675c59ba080ccdc52d8e74c7a"},
+ {file = "pyobjc_core-7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ffe61d3c2a404354daf2d895e34e38c5044453353581b3c396bf5365de26250c"},
]
pyobjc-framework-cocoa = [
- {file = "pyobjc-framework-Cocoa-7.1.tar.gz", hash = "sha256:67966152b3d38a0225176fceca2e9f56d849c8e7445548da09a00cb13155ec3e"},
- {file = "pyobjc_framework_Cocoa-7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bef77eafaac5eaf1d91d479d5483fd02216caa3edc27e8f5adc9af0b3fecdac3"},
- {file = "pyobjc_framework_Cocoa-7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2ea3582c456827dc20e648c905fdbcf8d3dfae89434f981e9b761cd07262049"},
- {file = "pyobjc_framework_Cocoa-7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a4050f2d776f40c2409a151c6f7896420e936934b3bdbfabedf91509637ed9b"},
- {file = "pyobjc_framework_Cocoa-7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f68f022f1f6d5985c418e10c6608c562fcf4bfe3714ec64fd10ce3dc6221bd4"},
- {file = "pyobjc_framework_Cocoa-7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ecfefd4c48dae42275c18679c69f6f2fff970e711097515a0a8732fc10194018"},
+ {file = "pyobjc-framework-Cocoa-7.2.tar.gz", hash = "sha256:c8b23f03dc3f4436d36c0fd006a8a084835c4f6015187df7c3aa5de8ecd5c653"},
+ {file = "pyobjc_framework_Cocoa-7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8e5dd5daa0096755937ec24c345a4b07c3fa131a457f99e0fdeeb01979178ec7"},
+ {file = "pyobjc_framework_Cocoa-7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:828d183947fc7746953fd0c9b1092cc423745ba0b49719e7b7d1e1614aaa20ec"},
+ {file = "pyobjc_framework_Cocoa-7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e4c6d7baa0c2ab5ea5efb8836ad0b3b3976cffcfc6195c1f195e826c6eb5744"},
+ {file = "pyobjc_framework_Cocoa-7.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9a9d1d49cc5a810773c88d6de821e60c8cc41d01113cf1b9e7662938f5f7d66"},
+ {file = "pyobjc_framework_Cocoa-7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:506c2cd09f421eac92b9008a0142174c3d1d70ecd4b0e3fa2b924767995fd14e"},
]
pyotp = [
{file = "pyotp-2.6.0-py2.py3-none-any.whl", hash = "sha256:9d144de0f8a601d6869abe1409f4a3f75f097c37b50a36a3bf165810a6e23f28"},
@@ -4400,6 +4456,10 @@ pytest = [
{file = "pytest-6.2.3-py3-none-any.whl", hash = "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc"},
{file = "pytest-6.2.3.tar.gz", hash = "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634"},
]
+python-binance = [
+ {file = "python-binance-1.0.1.tar.gz", hash = "sha256:7987189651938bb81a4e2f86993a32c77c55255d9c33891efb1d2bca918b8142"},
+ {file = "python_binance-1.0.1-py2.py3-none-any.whl", hash = "sha256:80dc4402837103024c44f0f57c5ac90c43e5ebe1da4f53104ebd4b9b788bd17d"},
+]
python-coinmarketcap = [
{file = "python-coinmarketcap-0.2.tar.gz", hash = "sha256:b1e0e8b098fc910a5ec943a06774d5ee3b190e915123366e2f16238ef0a8a433"},
{file = "python_coinmarketcap-0.2-py2-none-any.whl", hash = "sha256:54c278154ee11ca78837fb73eae8c322a60af93dc0ce75b515fecf895d181cf5"},
@@ -4422,8 +4482,8 @@ pytz = [
{file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
]
pyupgrade = [
- {file = "pyupgrade-2.13.0-py2.py3-none-any.whl", hash = "sha256:1cb5a15573fc63824b952bf7dde5b96f9672a1179ec779ac2d159a845da743c8"},
- {file = "pyupgrade-2.13.0.tar.gz", hash = "sha256:dc6b580b7bc82f357acb354bbba03d8d69da51425ecd5993a2651059bca80518"},
+ {file = "pyupgrade-2.14.0-py2.py3-none-any.whl", hash = "sha256:19bd23babd4c09171be66d393d83a53e5ae8d54c6534001c4b7ad92c4d560d69"},
+ {file = "pyupgrade-2.14.0.tar.gz", hash = "sha256:34f5dfa57d8007f6cd455a0a423e06e96753b07e4711bac63e3d5c4f3af97cea"},
]
pywin32 = [
{file = "pywin32-300-cp35-cp35m-win32.whl", hash = "sha256:1c204a81daed2089e55d11eefa4826c05e604d27fe2be40b6bf8db7b6a39da63"},
@@ -4528,8 +4588,8 @@ qdldl = [
{file = "qdldl-0.1.5.post0.tar.gz", hash = "sha256:c392c7427651d8b226423c7aba4a0f2338a1f38a4bbdabac6bc4afd8bc934f06"},
]
qtconsole = [
- {file = "qtconsole-5.0.3-py3-none-any.whl", hash = "sha256:4a38053993ca2da058f76f8d75b3d8906efbf9183de516f92f222ac8e37d9614"},
- {file = "qtconsole-5.0.3.tar.gz", hash = "sha256:c091a35607d2a2432e004c4a112d241ce908086570cf68594176dd52ccaa212d"},
+ {file = "qtconsole-5.1.0-py3-none-any.whl", hash = "sha256:3a2adecc43ff201a08972fb2179df22e7b3a08d71b9ed680f46ad1bfd4fb9132"},
+ {file = "qtconsole-5.1.0.tar.gz", hash = "sha256:12c734494901658787339dea9bbd82f3dc0d5e394071377a1c77b4a0954d7d8b"},
]
qtpy = [
{file = "QtPy-1.9.0-py2.py3-none-any.whl", hash = "sha256:fa0b8363b363e89b2a6f49eddc162a04c0699ae95e109a6be3bb145a913190ea"},
@@ -5009,6 +5069,33 @@ typing-extensions = [
{file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
{file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
]
+tzlocal = [
+ {file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"},
+ {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"},
+]
+ujson = [
+ {file = "ujson-4.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:e390df0dcc7897ffb98e17eae1f4c442c39c91814c298ad84d935a3c5c7a32fa"},
+ {file = "ujson-4.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:84b1dca0d53b0a8d58835f72ea2894e4d6cf7a5dd8f520ab4cbd698c81e49737"},
+ {file = "ujson-4.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:91396a585ba51f84dc71c8da60cdc86de6b60ba0272c389b6482020a1fac9394"},
+ {file = "ujson-4.0.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:eb6b25a7670c7537a5998e695fa62ff13c7f9c33faf82927adf4daa460d5f62e"},
+ {file = "ujson-4.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f8aded54c2bc554ce20b397f72101737dd61ee7b81c771684a7dd7805e6cca0c"},
+ {file = "ujson-4.0.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:30962467c36ff6de6161d784cd2a6aac1097f0128b522d6e9291678e34fb2b47"},
+ {file = "ujson-4.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:fc51e545d65689c398161f07fd405104956ec27f22453de85898fa088b2cd4bb"},
+ {file = "ujson-4.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e6e90330670c78e727d6637bb5a215d3e093d8e3570d439fd4922942f88da361"},
+ {file = "ujson-4.0.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:5e1636b94c7f1f59a8ead4c8a7bab1b12cc52d4c21ababa295ffec56b445fd2a"},
+ {file = "ujson-4.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:e2cadeb0ddc98e3963bea266cc5b884e5d77d73adf807f0bda9eca64d1c509d5"},
+ {file = "ujson-4.0.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:a214ba5a21dad71a43c0f5aef917cd56a2d70bc974d845be211c66b6742a471c"},
+ {file = "ujson-4.0.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0190d26c0e990c17ad072ec8593647218fe1c675d11089cd3d1440175b568967"},
+ {file = "ujson-4.0.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f273a875c0b42c2a019c337631bc1907f6fdfbc84210cc0d1fff0e2019bbfaec"},
+ {file = "ujson-4.0.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d3a87888c40b5bfcf69b4030427cd666893e826e82cc8608d1ba8b4b5e04ea99"},
+ {file = "ujson-4.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:7333e8bc45ea28c74ae26157eacaed5e5629dbada32e0103c23eb368f93af108"},
+ {file = "ujson-4.0.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b3a6dcc660220539aa718bcc9dbd6dedf2a01d19c875d1033f028f212e36d6bb"},
+ {file = "ujson-4.0.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:0ea07fe57f9157118ca689e7f6db72759395b99121c0ff038d2e38649c626fb1"},
+ {file = "ujson-4.0.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d6d061563470cac889c0a9fd367013a5dbd8efc36ad01ab3e67a57e56cad720"},
+ {file = "ujson-4.0.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b5c70704962cf93ec6ea3271a47d952b75ae1980d6c56b8496cec2a722075939"},
+ {file = "ujson-4.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:aad6d92f4d71e37ea70e966500f1951ecd065edca3a70d3861b37b176dd6702c"},
+ {file = "ujson-4.0.2.tar.gz", hash = "sha256:c615a9e9e378a7383b756b7e7a73c38b22aeb8967a8bfbffd4741f7ffd043c4d"},
+]
update-checker = [
{file = "update_checker-0.18.0-py3-none-any.whl", hash = "sha256:cbba64760a36fe2640d80d85306e8fe82b6816659190993b7bdabadee4d4bbfd"},
{file = "update_checker-0.18.0.tar.gz", hash = "sha256:6a2d45bb4ac585884a6b03f9eade9161cedd9e8111545141e9aa9058932acb13"},
diff --git a/pyproject.toml b/pyproject.toml
index 65ac1931eb2..43efb105290 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -63,6 +63,7 @@ oandapyV20 = "^0.6.3"
degiroapi = "^0.9.5"
valinvest = "^0.0.2"
bt = "^0.2.9"
+python-binance = "^1.0.1"
[tool.poetry.dev-dependencies]
pytest = "^6.2.2"
diff --git a/requirements-full.txt b/requirements-full.txt
index a8c07a921e4..a3f79bc3a5d 100644
--- a/requirements-full.txt
+++ b/requirements-full.txt
@@ -149,6 +149,7 @@ pyportfolioopt==1.4.1; python_full_version >= "3.6.1" and python_full_version <
pyrsistent==0.14.11
pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
pystan==2.19.1.1
+python-binance==1.0.1
python-coinmarketcap==0.2
python-dateutil==2.8.1; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") and (python_version >= "2.7" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "2.7" and python_version < "4" and python_full_version >= "3.3.0")
python-dotenv==0.17.0; python_version >= "3"
diff --git a/requirements-old.txt b/requirements-old.txt
index 77b359de143..71b5b6d736b 100644
--- a/requirements-old.txt
+++ b/requirements-old.txt
@@ -94,6 +94,7 @@ pyparsing==2.4.7; python_full_version >= "3.6.0" and python_version >= "3.6"
pyportfolioopt==1.4.1
pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
pystan==2.18
+python-binance==1.0.1
python-coinmarketcap==0.2
python-dateutil==2.8.1; python_full_version >= "3.6.1" and python_version >= "3.5" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") and (python_version >= "2.7" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "2.7" and python_version < "4" and python_full_version >= "3.3.0")
pytest==6.2.2; python_version >= "3.6"
diff --git a/requirements-win.txt b/requirements-win.txt
index 1f658e78cc9..b12bd43282f 100644
--- a/requirements-win.txt
+++ b/requirements-win.txt
@@ -96,6 +96,7 @@ pyparsing==2.4.7; python_full_version >= "3.6.0" and python_version >= "3.6"
pyportfolioopt==1.4.1
pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
pystan==2.19.1.1
+python-binance==1.0.1
python-coinmarketcap==0.2
python-dateutil==2.8.1; python_full_version >= "3.6.1" and python_version >= "3.5" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") and (python_version >= "2.7" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "2.7" and python_version < "4" and python_full_version >= "3.3.0")
pytest==6.2.2; python_version >= "3.6"
diff --git a/requirements.txt b/requirements.txt
index 4a189ab8556..04cdb8207db 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -113,6 +113,7 @@ pyparsing==2.4.7; python_version >= "3.6" and python_full_version < "3.0.0" or p
pyportfolioopt==1.4.1; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
pyrsistent==0.14.11
pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
+python-binance==1.0.1
python-coinmarketcap==0.2
python-dateutil==2.8.1; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0")
python-dotenv==0.17.0; python_version >= "3"