diff --git a/README.md b/README.md index 4e558e73cf5..55fbedbf1f2 100644 --- a/README.md +++ b/README.md @@ -227,10 +227,11 @@ These are the ones where a key is necessary: * Tradier: https://developer.tradier.com/getting_started * Oanda API: https://developer.oanda.com * CoinMarketCap API: https://coinmarketcap.com/api/ + * Finhub API: https://finnhub.io When these are obtained, don't forget to update [config_terminal.py](/gamestonk_terminal/config_terminal.py). -Alternatively, you can also set them to the following environment variables: GT_API_KEY_ALPHAVANTAGE, GT_API_KEY_FINANCIALMODELINGPREP, GT_API_KEY_QUANDL, GT_API_REDDIT_CLIENT_ID, GT_API_REDDIT_CLIENT_SECRET, GT_API_REDDIT_USERNAME, GT_API_REDDIT_USER_AGENT, GT_API_REDDIT_PASSWORD, GT_API_TWITTER_KEY, GT_API_TWITTER_SECRET_KEY, GT_API_TWITTER_BEARER_TOKEN, GT_API_POLYGON_KEY, GT_FRED_API_KEY, GT_API_NEWS_TOKEN, GT_OANDA_TOKEN, GT_OANDA_ACCOUNT, GT_TRADIER_TOKEN, GT_CMC_API_KEY. +Alternatively, you can also set them to the following environment variables: GT_API_KEY_ALPHAVANTAGE, GT_API_KEY_FINANCIALMODELINGPREP, GT_API_KEY_QUANDL, GT_API_REDDIT_CLIENT_ID, GT_API_REDDIT_CLIENT_SECRET, GT_API_REDDIT_USERNAME, GT_API_REDDIT_USER_AGENT, GT_API_REDDIT_PASSWORD, GT_API_TWITTER_KEY, GT_API_TWITTER_SECRET_KEY, GT_API_TWITTER_BEARER_TOKEN, GT_API_POLYGON_KEY, GT_FRED_API_KEY, GT_API_NEWS_TOKEN, GT_OANDA_TOKEN, GT_OANDA_ACCOUNT, GT_TRADIER_TOKEN, GT_CMC_API_KEY, GT_CMC_API_KEY, API_FINNHUB_KEY. Example: ``` diff --git a/ROADMAP.md b/ROADMAP.md index f872efde669..37c69ea2cb1 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -78,9 +78,10 @@ * [x] Add technical summary report provided by FinBrain (@didier) - [PR #294](https://github.com/DidierRLopes/GamestonkTerminal/pull/294) * [x] Add recommendation based on technical indicators from Tradingview (@didier) - [PR #301](https://github.com/DidierRLopes/GamestonkTerminal/pull/301) * [x] Add view of stock historical price with trendlines (support, resistance) by Finviz (@didier) - [PR #317](https://github.com/DidierRLopes/GamestonkTerminal/pull/317) +* [x] Add auto-recognition of major TA patterns (@didier) - [PR #429](https://github.com/DidierRLopes/GamestonkTerminal/pull/429) **NEXT** -* [ ] Add auto-recognition of major TA patterns (@didier) + --- diff --git a/gamestonk_terminal/README.md b/gamestonk_terminal/README.md index 5034728b90c..e314b341ba2 100644 --- a/gamestonk_terminal/README.md +++ b/gamestonk_terminal/README.md @@ -183,6 +183,7 @@ Command | Description | Sources `view` | view historical data and trendlines| [Finviz](https://finviz.com/quote.ashx?t=tsla) `summary` | technical summary report| [FinBrain](https://finbrain.tech) `recom` | recommendation based on Technical Indicators| [Tradingview](https://uk.tradingview.com/widget/technical-analysis/) +`pr` | pattern recognition| [Finnhub](https://finnhub.io) [overlap](https://github.com/twopirllc/pandas-ta/tree/master/pandas_ta/overlap) | `ema` | exponential moving average | [Wikipedia](https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average), [Investopedia](https://www.investopedia.com/terms/e/ema.asp) `sma` |simple moving average | [Wikipedia](https://en.wikipedia.org/wiki/Moving_average#Simple_moving_average_(boxcar_filter)), [Investopedia](https://www.investopedia.com/terms/s/sma.asp) @@ -397,7 +398,7 @@ positionbook |display the positionbook if Oanda provides one for the forex instr Command|Description ------ | -------- `ema` | buy when price exceeds EMA(l) -`ema_cross` | buy when EMA(short) > EMA(long) +`ema_cross` | buy when EMA(short) > EMA(long) `rsi` | buy when RSI < low and sell when RSI > high diff --git a/gamestonk_terminal/config_terminal.py b/gamestonk_terminal/config_terminal.py index dd4092eb45d..3dfa270ae34 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_FRED_API_KEY") or "b98e15f1fde39212bce9213f2e3e5afb" # https://newsapi.org API_NEWS_TOKEN = os.getenv("GT_API_NEWS_TOKEN") or "REPLACE_ME" @@ -59,3 +59,6 @@ PATH_TO_SELENIUM_DRIVER = None # Replace with "PATH" # https://coinmarketcap.com/api/ COINMARKETCAP_KEY = os.getenv("GT_CMC_API_KEY") or "REPLACE_ME" + +# https://finnhub.io +API_FINNHUB_KEY = os.getenv("GT_API_FINNHUB_KEY") or "REPLACE_ME" 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:/ Captura de ecrã 2021-03-31, às 00 14 41 +## 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'. + +![pr](https://user-images.githubusercontent.com/25267873/116812520-06338700-ab47-11eb-8db4-8f5e940507ed.png) + +Captura de ecrã 2021-05-02, às 02 27 10 + + # 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"""