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:/
+## 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"""