Files
OpenBB/openbb_terminal/stocks/comparison_analysis/yahoo_finance_model.py
James Simmons 53d59a8691 fixing load and volume (#3606)
Co-authored-by: DidierRLopes <dro.lopes@campus.fct.unl.pt>
2022-11-27 09:26:36 +00:00

234 lines
6.8 KiB
Python

"""Yahoo Finance Comparison Model"""
__docformat__ = "numpy"
import logging
from datetime import datetime, timedelta
from typing import List, Optional, Tuple
import warnings
import numpy as np
import pandas as pd
import yfinance as yf
from sklearn.manifold import TSNE
from sklearn.preprocessing import normalize
from openbb_terminal.decorators import log_start_end
from openbb_terminal.rich_config import console
logger = logging.getLogger(__name__)
d_candle_types = {
"o": "Open",
"h": "High",
"l": "Low",
"c": "Close",
"a": "Adj Close",
"v": "Volume",
}
@log_start_end(log=logger)
def get_historical(
similar: List[str],
start_date: Optional[str] = None,
candle_type: str = "a",
) -> pd.DataFrame:
"""Get historical prices for all comparison stocks
Parameters
----------
similar: List[str]
List of similar tickers.
Comparable companies can be accessed through
finnhub_peers(), finviz_peers(), polygon_peers().
start_date: Optional[str], optional
Initial date (e.g., 2021-10-01). Defaults to 1 year back
candle_type: str, optional
Candle variable to compare, by default "a" for Adjusted Close. Possible values are: o, h, l, c, a, v, r
Returns
-------
pd.DataFrame
Dataframe containing candle type variable for each ticker
"""
if start_date is None:
start_date = (datetime.now() - timedelta(days=366)).strftime("%Y-%m-%d")
candle_type = candle_type.lower()
use_returns = False
if candle_type == "r":
# Calculate returns based off of adjusted close
use_returns = True
candle_type = "a"
# To avoid having to recursively append, just do a single yfinance call. This will give dataframe
# where all tickers are columns.
similar_tickers_dataframe = yf.download(
similar, start=start_date, progress=False, threads=False
)[d_candle_types[candle_type]]
returnable = (
similar_tickers_dataframe
if similar_tickers_dataframe.empty
else similar_tickers_dataframe[similar]
)
if use_returns:
# To calculate the period to period return,
# shift the dataframe by one row, then divide it into
# the other, then subtract 1 to get a percentage, which is the return.
shifted = returnable.shift(1)[1:]
returnable = returnable.div(shifted) - 1
df_similar = returnable[similar]
if np.any(df_similar.isna()):
nan_tickers = df_similar.columns[df_similar.isna().sum() >= 1].to_list()
console.print(
f"NaN values found in: {', '.join(nan_tickers)}. Backfilling data"
)
df_similar = df_similar.fillna(method="bfill")
df_similar = df_similar.dropna(axis=1, how="all")
return df_similar
@log_start_end(log=logger)
def get_correlation(
similar: List[str],
start_date: Optional[str] = None,
candle_type: str = "a",
) -> Tuple[pd.DataFrame, pd.DataFrame]:
"""
Get historical price correlation. [Source: Yahoo Finance]
Parameters
----------
similar : List[str]
List of similar tickers.
Comparable companies can be accessed through
finnhub_peers(), finviz_peers(), polygon_peers().
start_date : Optional[str], optional
Initial date (e.g., 2021-10-01). Defaults to 1 year back
candle_type : str, optional
OHLCA column to use for candles or R for returns, by default "a" for Adjusted Close
Returns
-------
Tuple[pd.DataFrame, pd.DataFrame]
Dataframe with correlation matrix, Dataframe with historical prices for all comparison stocks
"""
if start_date is None:
start_date = (datetime.now() - timedelta(days=366)).strftime("%Y-%m-%d")
df_similar = get_historical(similar, start_date, candle_type)
correlations = df_similar.corr()
return correlations, df_similar
@log_start_end(log=logger)
def get_volume(
similar: List[str],
start_date: Optional[str] = None,
) -> pd.DataFrame:
"""Get stock volume. [Source: Yahoo Finance]
Parameters
----------
similar : List[str]
List of similar tickers.
Comparable companies can be accessed through
finnhub_peers(), finviz_peers(), polygon_peers().
start_date : Optional[str], optional
Initial date (e.g., 2021-10-01). Defaults to 1 year back
Returns
-------
pd.DataFrame
Dataframe with volume for stock
"""
if start_date is None:
start_date = (datetime.now() - timedelta(days=366)).strftime("%Y-%m-%d")
df_similar = get_historical(similar, start_date, "v")
df_similar = df_similar[df_similar.columns]
return df_similar
@log_start_end(log=logger)
def get_1y_sp500() -> pd.DataFrame:
"""
Gets the last year of Adj Close prices for all current SP 500 stocks.
They are scraped daily using yfinance at https://github.com/jmaslek/daily_sp_500
Returns
-------
pd.DataFrame
DataFrame containing last 1 year of closes for all SP500 stocks.
"""
return pd.read_csv(
"https://raw.githubusercontent.com/jmaslek/daily_sp_500/main/SP500_prices_1yr.csv",
index_col=0,
)
# pylint:disable=E1137,E1101
@log_start_end(log=logger)
def get_sp500_comps_tsne(
symbol: str,
lr: int = 200,
) -> pd.DataFrame:
"""
Runs TSNE on SP500 tickers (along with ticker if not in SP500).
TSNE is a method of visualing higher dimensional data
https://scikit-learn.org/stable/modules/generated/sklearn.manifold.TSNE.html
Note that the TSNE numbers are meaningless and will be arbitrary if run again.
Parameters
----------
symbol: str
Ticker to get comparisons to
lr: int
Learning rate for TSNE
Returns
-------
pd.DataFrame
Dataframe of tickers closest to selected ticker
"""
# Adding the type makes pylint stop yelling
close_vals: pd.DataFrame = get_1y_sp500()
if symbol not in close_vals.columns:
df_symbol = yf.download(symbol, start=close_vals.index[0], progress=False)[
"Adj Close"
].to_frame()
df_symbol.columns = [symbol]
df_symbol.index = df_symbol.index.astype(str)
close_vals = close_vals.join(df_symbol)
close_vals = close_vals.fillna(method="bfill")
rets = close_vals.pct_change()[1:].T
# Future warning from sklearn. Think 1.2 will stop printing it
warnings.filterwarnings("ignore", category=FutureWarning)
model = TSNE(learning_rate=lr, init="pca")
tsne_features = model.fit_transform(normalize(rets))
warnings.resetwarnings()
xs = tsne_features[:, 0]
ys = tsne_features[:, 1]
data = pd.DataFrame({"X": xs, "Y": ys}, index=rets.index)
x0, y0 = data.loc[symbol]
data["dist"] = (data.X - x0) ** 2 + (data.Y - y0) ** 2
data = data.sort_values(by="dist")
return data