mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 04:31:13 +00:00
1008 lines
30 KiB
Python
1008 lines
30 KiB
Python
"""Main helper"""
|
|
__docformat__ = "numpy"
|
|
import argparse
|
|
import json
|
|
import warnings
|
|
from datetime import datetime, timedelta
|
|
from typing import List, Union
|
|
|
|
import matplotlib.pyplot as plt
|
|
import mplfinance as mpf
|
|
import pandas as pd
|
|
import plotly.graph_objects as go
|
|
import pyEX
|
|
import pytz
|
|
import requests
|
|
import yfinance as yf
|
|
from alpha_vantage.timeseries import TimeSeries
|
|
from numpy.core.fromnumeric import transpose
|
|
from plotly.subplots import make_subplots
|
|
from scipy import stats
|
|
from tabulate import tabulate
|
|
|
|
from gamestonk_terminal import config_plot as cfgPlot
|
|
from gamestonk_terminal import config_terminal as cfg
|
|
from gamestonk_terminal import feature_flags as gtff
|
|
from gamestonk_terminal.helper_funcs import (
|
|
parse_known_args_and_warn,
|
|
plot_autoscale,
|
|
valid_date,
|
|
)
|
|
|
|
warnings.simplefilter("ignore")
|
|
# pylint: disable=no-member,too-many-branches,C0302
|
|
|
|
|
|
def clear(
|
|
other_args: List[str],
|
|
s_ticker: str,
|
|
s_start,
|
|
s_interval: str,
|
|
df_stock: pd.DataFrame,
|
|
):
|
|
"""Clears loaded stock and returns empty variables
|
|
|
|
Parameters
|
|
----------
|
|
other_args : List[str]
|
|
Argparse arguments
|
|
s_ticker : str
|
|
Ticker
|
|
s_start : str
|
|
Start date
|
|
s_interval : str
|
|
Interval to get data for
|
|
df_stock : pd.DataFrame
|
|
Preloaded dataframe
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
Ticker
|
|
str
|
|
Start date
|
|
str
|
|
Interval
|
|
pd.DataFrame
|
|
Dataframe of data
|
|
"""
|
|
parser = argparse.ArgumentParser(
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
prog="clear",
|
|
description="""Clear previously loaded stock ticker.""",
|
|
)
|
|
|
|
try:
|
|
ns_parser = parse_known_args_and_warn(parser, other_args)
|
|
if not ns_parser:
|
|
return "", "", "", pd.DataFrame()
|
|
|
|
print("Clearing stock ticker to be used for analysis\n")
|
|
return "", "", "", pd.DataFrame()
|
|
|
|
except SystemExit:
|
|
print("")
|
|
return s_ticker, s_start, s_interval, df_stock
|
|
|
|
|
|
def search(
|
|
query: str,
|
|
amount: int,
|
|
):
|
|
"""Search selected query for tickers.
|
|
|
|
Parameters
|
|
----------
|
|
query : str
|
|
The search term used to find company tickers.
|
|
amount : int
|
|
The amount of companies shown.
|
|
|
|
Returns
|
|
-------
|
|
tabulate
|
|
Companies that match the query.
|
|
"""
|
|
equities_list = (
|
|
"https://raw.githubusercontent.com/JerBouma/FinanceDatabase/master/"
|
|
"Database/Equities/Equities List.json"
|
|
)
|
|
request = requests.get(equities_list)
|
|
equities = json.loads(request.text)
|
|
|
|
equities_query = {
|
|
key: value
|
|
for key, value in equities.items()
|
|
if (query in key.lower()) or (query in value.lower())
|
|
}
|
|
|
|
equities_dataframe = pd.DataFrame(
|
|
equities_query.items(),
|
|
index=equities_query.values(),
|
|
columns=["Company", "Ticker"],
|
|
)
|
|
|
|
if equities_dataframe.empty:
|
|
raise ValueError("No companies found. \n")
|
|
|
|
if gtff.USE_TABULATE_DF:
|
|
print(
|
|
tabulate(
|
|
equities_dataframe.iloc[:amount],
|
|
showindex=False,
|
|
headers=["Company", "Ticker"],
|
|
tablefmt="fancy_grid",
|
|
),
|
|
"\n",
|
|
)
|
|
else:
|
|
print(equities_dataframe.iloc[:amount].to_string(), "\n")
|
|
|
|
|
|
def load(
|
|
other_args: List[str],
|
|
s_ticker: str,
|
|
s_start,
|
|
s_interval: str,
|
|
df_stock: pd.DataFrame,
|
|
):
|
|
"""Load selected ticker
|
|
|
|
Parameters
|
|
----------
|
|
other_args : List[str]
|
|
Argparse arguments
|
|
s_ticker : str
|
|
Ticker
|
|
s_start : str
|
|
Start date
|
|
s_interval : str
|
|
Interval to get data for
|
|
df_stock : pd.DataFrame
|
|
Preloaded dataframe
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
Ticker
|
|
str
|
|
Start date
|
|
str
|
|
Interval
|
|
pd.DataFrame
|
|
Dataframe of data.
|
|
"""
|
|
parser = argparse.ArgumentParser(
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
prog="load",
|
|
description="Load stock ticker to perform analysis on. When the data source is 'yf', an Indian ticker can be"
|
|
" loaded by using '.NS' at the end, e.g. 'SBIN.NS'. See available market in"
|
|
" https://help.yahoo.com/kb/exchanges-data-providers-yahoo-finance-sln2310.html.",
|
|
)
|
|
parser.add_argument(
|
|
"-t",
|
|
"--ticker",
|
|
action="store",
|
|
dest="s_ticker",
|
|
required="-h" not in other_args,
|
|
help="Stock ticker",
|
|
)
|
|
parser.add_argument(
|
|
"-s",
|
|
"--start",
|
|
type=valid_date,
|
|
default=(datetime.now() - timedelta(days=366)).strftime("%Y-%m-%d"),
|
|
dest="s_start_date",
|
|
help="The starting date (format YYYY-MM-DD) of the stock",
|
|
)
|
|
parser.add_argument(
|
|
"-e",
|
|
"--end",
|
|
type=valid_date,
|
|
default=datetime.now().strftime("%Y-%m-%d"),
|
|
dest="s_end_date",
|
|
help="The ending date (format YYYY-MM-DD) of the stock",
|
|
)
|
|
parser.add_argument(
|
|
"-i",
|
|
"--interval",
|
|
action="store",
|
|
dest="n_interval",
|
|
type=int,
|
|
default=1440,
|
|
choices=[1, 5, 15, 30, 60],
|
|
help="Intraday stock minutes",
|
|
)
|
|
parser.add_argument(
|
|
"--source",
|
|
action="store",
|
|
dest="source",
|
|
choices=["yf", "av", "iex"],
|
|
default="yf",
|
|
help="Source of historical data.",
|
|
)
|
|
parser.add_argument(
|
|
"-p",
|
|
"--prepost",
|
|
action="store_true",
|
|
default=False,
|
|
dest="b_prepost",
|
|
help="Pre/After market hours. Only works for 'yf' source, and intraday data",
|
|
)
|
|
|
|
try:
|
|
# For the case where a user uses: 'load BB'
|
|
if other_args and "-t" not in other_args and "-h" not in other_args:
|
|
other_args.insert(0, "-t")
|
|
|
|
ns_parser = parse_known_args_and_warn(parser, other_args)
|
|
if not ns_parser:
|
|
return [s_ticker, s_start, s_interval, df_stock]
|
|
|
|
# Daily
|
|
if ns_parser.n_interval == 1440:
|
|
|
|
# Alpha Vantage Source
|
|
if ns_parser.source == "av":
|
|
ts = TimeSeries(key=cfg.API_KEY_ALPHAVANTAGE, output_format="pandas")
|
|
# pylint: disable=unbalanced-tuple-unpacking
|
|
df_stock_candidate, _ = ts.get_daily_adjusted(
|
|
symbol=ns_parser.s_ticker, outputsize="full"
|
|
)
|
|
|
|
df_stock_candidate.columns = [
|
|
val.split(". ")[1].capitalize()
|
|
for val in df_stock_candidate.columns
|
|
]
|
|
|
|
df_stock_candidate = df_stock_candidate.rename(
|
|
columns={
|
|
"Adjusted close": "Adj Close",
|
|
}
|
|
)
|
|
|
|
# Check that loading a stock was not successful
|
|
# pylint: disable=no-member
|
|
if df_stock_candidate.empty:
|
|
print("")
|
|
return [s_ticker, s_start, s_interval, df_stock]
|
|
|
|
df_stock_candidate.index = df_stock_candidate.index.tz_localize(None)
|
|
|
|
# pylint: disable=no-member
|
|
df_stock_candidate.sort_index(ascending=True, inplace=True)
|
|
|
|
# Slice dataframe from the starting date YYYY-MM-DD selected
|
|
df_stock_candidate = df_stock_candidate[
|
|
ns_parser.s_start_date : ns_parser.s_end_date
|
|
]
|
|
|
|
# Yahoo Finance Source
|
|
elif ns_parser.source == "yf":
|
|
df_stock_candidate = yf.download(
|
|
ns_parser.s_ticker,
|
|
start=ns_parser.s_start_date,
|
|
end=ns_parser.s_end_date,
|
|
progress=False,
|
|
)
|
|
|
|
# Check that loading a stock was not successful
|
|
if df_stock_candidate.empty:
|
|
print("")
|
|
return [s_ticker, s_start, s_interval, df_stock]
|
|
|
|
df_stock_candidate.index.name = "date"
|
|
|
|
# IEX Cloud Source
|
|
elif ns_parser.source == "iex":
|
|
client = pyEX.Client(api_token=cfg.API_IEX_TOKEN, version="v1")
|
|
|
|
df_stock_candidate = client.chartDF(ns_parser.s_ticker)
|
|
|
|
# Check that loading a stock was not successful
|
|
if df_stock_candidate.empty:
|
|
print("")
|
|
return [s_ticker, s_start, s_interval, df_stock]
|
|
|
|
df_stock_candidate = df_stock_candidate[
|
|
["uClose", "uHigh", "uLow", "uOpen", "fClose", "volume"]
|
|
]
|
|
df_stock_candidate = df_stock_candidate.rename(
|
|
columns={
|
|
"uClose": "Close",
|
|
"uHigh": "High",
|
|
"uLow": "Low",
|
|
"uOpen": "Open",
|
|
"fClose": "Adj Close",
|
|
"volume": "Volume",
|
|
}
|
|
)
|
|
|
|
df_stock_candidate.sort_index(ascending=True, inplace=True)
|
|
|
|
# Slice dataframe from the starting date YYYY-MM-DD selected
|
|
df_stock_candidate = df_stock_candidate[
|
|
ns_parser.s_start_date : ns_parser.s_end_date
|
|
]
|
|
|
|
# Check if start time from dataframe is more recent than specified
|
|
if df_stock_candidate.index[0] > pd.to_datetime(ns_parser.s_start_date):
|
|
s_start = df_stock_candidate.index[0]
|
|
else:
|
|
s_start = ns_parser.s_start_date
|
|
|
|
elif ns_parser.source == "av":
|
|
ts = TimeSeries(key=cfg.API_KEY_ALPHAVANTAGE, output_format="pandas")
|
|
# pylint: disable=unbalanced-tuple-unpacking
|
|
df_stock_candidate, _ = ts.get_intraday(
|
|
symbol=ns_parser.s_ticker,
|
|
outputsize="full",
|
|
interval=str(ns_parser.n_interval) + "min",
|
|
)
|
|
|
|
df_stock_candidate.columns = [
|
|
val.split(". ")[1].capitalize() for val in df_stock_candidate.columns
|
|
]
|
|
|
|
df_stock_candidate = df_stock_candidate.rename(
|
|
columns={
|
|
"Adjusted close": "Adj Close",
|
|
}
|
|
)
|
|
|
|
s_interval = str(ns_parser.n_interval) + "min"
|
|
# Check that loading a stock was not successful
|
|
# pylint: disable=no-member
|
|
if df_stock_candidate.empty:
|
|
print("")
|
|
return [s_ticker, s_start, s_interval, df_stock]
|
|
|
|
# pylint: disable=no-member
|
|
df_stock_candidate.sort_index(ascending=True, inplace=True)
|
|
|
|
# Slice dataframe from the starting date YYYY-MM-DD selected
|
|
df_stock_candidate = df_stock_candidate[
|
|
ns_parser.s_start_date : ns_parser.s_end_date
|
|
]
|
|
|
|
# Check if start time from dataframe is more recent than specified
|
|
if df_stock_candidate.index[0] > pd.to_datetime(ns_parser.s_start_date):
|
|
s_start = df_stock_candidate.index[0]
|
|
else:
|
|
s_start = ns_parser.s_start_date
|
|
|
|
elif ns_parser.source == "yf":
|
|
s_int = str(ns_parser.n_interval) + "m"
|
|
s_interval = s_int + "in"
|
|
d_granularity = {"1m": 6, "5m": 59, "15m": 59, "30m": 59, "60m": 729}
|
|
|
|
s_start_dt = datetime.utcnow() - timedelta(days=d_granularity[s_int])
|
|
s_date_start = s_start_dt.strftime("%Y-%m-%d")
|
|
|
|
if s_start_dt > ns_parser.s_start_date:
|
|
# Using Yahoo Finance with granularity {s_int} the starting date is set to: {s_date_start}
|
|
|
|
df_stock_candidate = yf.download(
|
|
ns_parser.s_ticker,
|
|
start=s_date_start,
|
|
progress=False,
|
|
interval=s_int,
|
|
prepost=ns_parser.b_prepost,
|
|
)
|
|
|
|
else:
|
|
df_stock_candidate = yf.download(
|
|
ns_parser.s_ticker,
|
|
start=ns_parser.s_start_date.strftime("%Y-%m-%d"),
|
|
progress=False,
|
|
interval=s_int,
|
|
prepost=ns_parser.b_prepost,
|
|
)
|
|
|
|
# Check that loading a stock was not successful
|
|
if df_stock_candidate.empty:
|
|
print("")
|
|
return [s_ticker, s_start, s_interval, df_stock]
|
|
|
|
df_stock_candidate.index = df_stock_candidate.index.tz_localize(None)
|
|
|
|
if s_start_dt > ns_parser.s_start_date:
|
|
s_start = pytz.utc.localize(s_start_dt)
|
|
else:
|
|
s_start = ns_parser.s_start_date
|
|
|
|
df_stock_candidate.index.name = "date"
|
|
|
|
elif ns_parser.source == "iex":
|
|
|
|
s_interval = str(ns_parser.n_interval) + "min"
|
|
client = pyEX.Client(api_token=cfg.API_IEX_TOKEN, version="v1")
|
|
|
|
df_stock_candidate = client.chartDF(ns_parser.s_ticker)
|
|
|
|
df_stock_candidate = client.intradayDF(ns_parser.s_ticker).iloc[
|
|
0 :: ns_parser.n_interval
|
|
]
|
|
|
|
df_stock_candidate = df_stock_candidate[
|
|
["close", "high", "low", "open", "volume", "close"]
|
|
]
|
|
df_stock_candidate.columns = [
|
|
x.capitalize() for x in df_stock_candidate.columns
|
|
]
|
|
|
|
df_stock_candidate.columns = list(df_stock_candidate.columns[:-1]) + [
|
|
"Adj Close"
|
|
]
|
|
|
|
df_stock_candidate.sort_index(ascending=True, inplace=True)
|
|
|
|
new_index = []
|
|
for idx in range(len(df_stock_candidate)):
|
|
dt_time = datetime.strptime(df_stock_candidate.index[idx][1], "%H:%M")
|
|
new_index.append(
|
|
df_stock_candidate.index[idx][0]
|
|
+ timedelta(hours=dt_time.hour, minutes=dt_time.minute)
|
|
)
|
|
|
|
df_stock_candidate.index = pd.DatetimeIndex(new_index)
|
|
df_stock_candidate.index.name = "date"
|
|
|
|
# Slice dataframe from the starting date YYYY-MM-DD selected
|
|
df_stock_candidate = df_stock_candidate[
|
|
ns_parser.s_start_date : ns_parser.s_end_date
|
|
]
|
|
|
|
# Check if start time from dataframe is more recent than specified
|
|
if df_stock_candidate.index[0] > pd.to_datetime(ns_parser.s_start_date):
|
|
s_start = df_stock_candidate.index[0]
|
|
else:
|
|
s_start = ns_parser.s_start_date
|
|
|
|
s_intraday = (f"Intraday {s_interval}", "Daily")[ns_parser.n_interval == 1440]
|
|
|
|
print(
|
|
f"Loading {s_intraday} {ns_parser.s_ticker.upper()} stock "
|
|
f"with starting period {s_start.strftime('%Y-%m-%d')} for analysis.\n"
|
|
)
|
|
|
|
return [
|
|
ns_parser.s_ticker.upper(),
|
|
s_start,
|
|
str(ns_parser.n_interval) + "min",
|
|
df_stock_candidate,
|
|
]
|
|
|
|
except Exception as e:
|
|
print(e, "\nEither the ticker or the API_KEY are invalids. Try again!\n")
|
|
return [s_ticker, s_start, s_interval, df_stock]
|
|
|
|
except SystemExit:
|
|
print("")
|
|
return [s_ticker, s_start, s_interval, df_stock]
|
|
|
|
|
|
def display_candle(s_ticker: str, df_stock: pd.DataFrame, use_matplotlib: bool):
|
|
"""Shows candle plot of loaded ticker. [Source: Yahoo Finance, IEX Cloud or Alpha Vantage]
|
|
|
|
Parameters
|
|
----------
|
|
df_stock: pd.DataFrame
|
|
Stock dataframe
|
|
s_ticker: str
|
|
Ticker name
|
|
use_matplotlib: bool
|
|
Flag to use matplotlib instead of interactive plotly chart
|
|
"""
|
|
df_stock["ma20"] = df_stock["Close"].rolling(20).mean().fillna(method="bfill")
|
|
df_stock["ma50"] = df_stock["Close"].rolling(50).mean().fillna(method="bfill")
|
|
|
|
if (df_stock.index[1] - df_stock.index[0]).total_seconds() >= 86400:
|
|
df_stock = find_trendline(df_stock, "OC_High", "high")
|
|
df_stock = find_trendline(df_stock, "OC_Low", "low")
|
|
|
|
if use_matplotlib:
|
|
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=True)
|
|
|
|
ap0 = []
|
|
|
|
if "OC_High_trend" in df_stock.columns:
|
|
ap0.append(
|
|
mpf.make_addplot(df_stock["OC_High_trend"], color="g"),
|
|
)
|
|
|
|
if "OC_Low_trend" in df_stock.columns:
|
|
ap0.append(
|
|
mpf.make_addplot(df_stock["OC_Low_trend"], color="b"),
|
|
)
|
|
|
|
if gtff.USE_ION:
|
|
plt.ion()
|
|
|
|
mpf.plot(
|
|
df_stock,
|
|
type="candle",
|
|
mav=(20, 50),
|
|
volume=True,
|
|
title=f"\nStock {s_ticker}",
|
|
addplot=ap0,
|
|
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
|
|
),
|
|
)
|
|
else:
|
|
fig = make_subplots(
|
|
rows=2,
|
|
cols=1,
|
|
shared_xaxes=True,
|
|
vertical_spacing=0.06,
|
|
subplot_titles=(f"{s_ticker}", "Volume"),
|
|
row_width=[0.2, 0.7],
|
|
)
|
|
fig.add_trace(
|
|
go.Candlestick(
|
|
x=df_stock.index,
|
|
open=df_stock.Open,
|
|
high=df_stock.High,
|
|
low=df_stock.Low,
|
|
close=df_stock.Close,
|
|
name="OHLC",
|
|
),
|
|
row=1,
|
|
col=1,
|
|
)
|
|
fig.add_trace(
|
|
go.Scatter(
|
|
x=df_stock.index,
|
|
y=df_stock["ma20"],
|
|
name="MA20",
|
|
mode="lines",
|
|
line=go.scatter.Line(color="royalblue"),
|
|
),
|
|
row=1,
|
|
col=1,
|
|
)
|
|
fig.add_trace(
|
|
go.Scatter(
|
|
x=df_stock.index,
|
|
y=df_stock["ma50"],
|
|
name="MA50",
|
|
mode="lines",
|
|
line=go.scatter.Line(color="black"),
|
|
),
|
|
row=1,
|
|
col=1,
|
|
)
|
|
|
|
if "OC_High_trend" in df_stock.columns:
|
|
fig.add_trace(
|
|
go.Scatter(
|
|
x=df_stock.index,
|
|
y=df_stock["OC_High_trend"],
|
|
name="High Trend",
|
|
mode="lines",
|
|
line=go.scatter.Line(color="green"),
|
|
),
|
|
row=1,
|
|
col=1,
|
|
)
|
|
if "OC_Low_trend" in df_stock.columns:
|
|
fig.add_trace(
|
|
go.Scatter(
|
|
x=df_stock.index,
|
|
y=df_stock["OC_Low_trend"],
|
|
name="Low Trend",
|
|
mode="lines",
|
|
line=go.scatter.Line(color="red"),
|
|
),
|
|
row=1,
|
|
col=1,
|
|
)
|
|
|
|
fig.add_trace(
|
|
go.Bar(
|
|
x=df_stock.index,
|
|
y=df_stock.Volume,
|
|
name="Volume",
|
|
marker_color="#696969",
|
|
),
|
|
row=2,
|
|
col=1,
|
|
)
|
|
fig.update_layout(
|
|
yaxis_title="Stock Price ($)",
|
|
xaxis=dict(
|
|
rangeselector=dict(
|
|
buttons=list(
|
|
[
|
|
dict(
|
|
count=1,
|
|
label="1m",
|
|
step="month",
|
|
stepmode="backward",
|
|
),
|
|
dict(
|
|
count=3,
|
|
label="3m",
|
|
step="month",
|
|
stepmode="backward",
|
|
),
|
|
dict(count=1, label="YTD", step="year", stepmode="todate"),
|
|
dict(
|
|
count=1,
|
|
label="1y",
|
|
step="year",
|
|
stepmode="backward",
|
|
),
|
|
dict(step="all"),
|
|
]
|
|
)
|
|
),
|
|
rangeslider=dict(visible=False),
|
|
type="date",
|
|
),
|
|
)
|
|
|
|
fig.show()
|
|
print("")
|
|
|
|
|
|
def quote(other_args: List[str], s_ticker: str):
|
|
"""Ticker quote
|
|
|
|
Parameters
|
|
----------
|
|
other_args : List[str]
|
|
Argparse arguments
|
|
s_ticker : str
|
|
Ticker
|
|
"""
|
|
parser = argparse.ArgumentParser(
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
prog="quote",
|
|
description="Current quote for stock ticker",
|
|
)
|
|
|
|
if s_ticker:
|
|
parser.add_argument(
|
|
"-t",
|
|
"--ticker",
|
|
action="store",
|
|
dest="s_ticker",
|
|
default=s_ticker,
|
|
help="Stock ticker",
|
|
)
|
|
else:
|
|
parser.add_argument(
|
|
"-t",
|
|
"--ticker",
|
|
action="store",
|
|
dest="s_ticker",
|
|
required=True,
|
|
help="Stock ticker",
|
|
)
|
|
|
|
try:
|
|
# For the case where a user uses: 'quote BB'
|
|
if other_args and "-" not in other_args[0]:
|
|
other_args.insert(0, "-t")
|
|
ns_parser = parse_known_args_and_warn(parser, other_args)
|
|
if not ns_parser:
|
|
return
|
|
|
|
except SystemExit:
|
|
print("")
|
|
return
|
|
|
|
ticker = yf.Ticker(ns_parser.s_ticker)
|
|
|
|
try:
|
|
quote_df = pd.DataFrame(
|
|
[
|
|
{
|
|
"Symbol": ticker.info["symbol"],
|
|
"Name": ticker.info["shortName"],
|
|
"Price": ticker.info["regularMarketPrice"],
|
|
"Open": ticker.info["regularMarketOpen"],
|
|
"High": ticker.info["dayHigh"],
|
|
"Low": ticker.info["dayLow"],
|
|
"Previous Close": ticker.info["previousClose"],
|
|
"Volume": ticker.info["volume"],
|
|
"52 Week High": ticker.info["fiftyTwoWeekHigh"],
|
|
"52 Week Low": ticker.info["fiftyTwoWeekLow"],
|
|
}
|
|
]
|
|
)
|
|
|
|
quote_df["Change"] = quote_df["Price"] - quote_df["Previous Close"]
|
|
quote_df["Change %"] = quote_df.apply(
|
|
lambda x: f'{((x["Change"] / x["Previous Close"]) * 100):.2f}%',
|
|
axis="columns",
|
|
)
|
|
for c in [
|
|
"Price",
|
|
"Open",
|
|
"High",
|
|
"Low",
|
|
"Previous Close",
|
|
"52 Week High",
|
|
"52 Week Low",
|
|
"Change",
|
|
]:
|
|
quote_df[c] = quote_df[c].apply(lambda x: f"{x:.2f}")
|
|
quote_df["Volume"] = quote_df["Volume"].apply(lambda x: f"{x:,}")
|
|
|
|
quote_df = quote_df.set_index("Symbol")
|
|
|
|
quote_data = transpose(quote_df)
|
|
|
|
print(
|
|
tabulate(
|
|
quote_data,
|
|
headers=quote_data.columns,
|
|
tablefmt="fancy_grid",
|
|
stralign="right",
|
|
)
|
|
)
|
|
except KeyError:
|
|
print(f"Invalid stock ticker: {ns_parser.s_ticker}")
|
|
|
|
print("")
|
|
return
|
|
|
|
|
|
def view(other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame):
|
|
"""Plot loaded ticker
|
|
|
|
Parameters
|
|
----------
|
|
other_args : List[str]
|
|
Argparse arguments
|
|
s_ticker : str
|
|
Ticker to load
|
|
s_interval : str
|
|
Interval tto get data for
|
|
df_stock : pd.Dataframe
|
|
Preloaded dataframe to plot
|
|
"""
|
|
parser = argparse.ArgumentParser(
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
prog="view",
|
|
description="Visualize historical data of a stock.",
|
|
)
|
|
|
|
try:
|
|
ns_parser = parse_known_args_and_warn(parser, other_args)
|
|
if not ns_parser:
|
|
return
|
|
|
|
if not s_ticker:
|
|
print("No ticker loaded. First use `load {ticker}`", "\n")
|
|
return
|
|
|
|
# Plot view of the stock
|
|
plot_view_stock(df_stock, s_ticker, s_interval)
|
|
|
|
except Exception as e:
|
|
print("Error in plotting:")
|
|
print(e, "\n")
|
|
|
|
except SystemExit:
|
|
print("")
|
|
return
|
|
|
|
|
|
def plot_view_stock(df: pd.DataFrame, symbol: str, interval: str):
|
|
"""
|
|
Plot the loaded stock dataframe
|
|
Parameters
|
|
----------
|
|
df: Dataframe
|
|
Dataframe of prices and volumnes
|
|
symbol: str
|
|
Symbol of ticker
|
|
interval: str
|
|
Stock data resolution for plotting purposes
|
|
|
|
"""
|
|
df.sort_index(ascending=True, inplace=True)
|
|
bar_colors = ["r" if x[1].Open < x[1].Close else "g" for x in df.iterrows()]
|
|
|
|
try:
|
|
fig, ax = plt.subplots(
|
|
2,
|
|
1,
|
|
gridspec_kw={"height_ratios": [3, 1]},
|
|
figsize=plot_autoscale(),
|
|
dpi=cfgPlot.PLOT_DPI,
|
|
)
|
|
except Exception as e:
|
|
print(e)
|
|
print(
|
|
"Encountered an error trying to open a chart window. Check your X server configuration."
|
|
)
|
|
return
|
|
|
|
# In order to make nice Volume plot, make the bar width = interval
|
|
if interval == "1440min":
|
|
bar_width = timedelta(days=1)
|
|
title_string = "Daily"
|
|
else:
|
|
bar_width = timedelta(minutes=int(interval.split("m")[0]))
|
|
title_string = f"{int(interval.split('m')[0])} min"
|
|
|
|
ax[0].yaxis.tick_right()
|
|
if "Adj Close" in df.columns:
|
|
ax[0].plot(df.index, df["Adj Close"], c=cfgPlot.VIEW_COLOR)
|
|
else:
|
|
ax[0].plot(df.index, df["Close"], c=cfgPlot.VIEW_COLOR)
|
|
ax[0].set_xlim(df.index[0], df.index[-1])
|
|
ax[0].set_xticks([])
|
|
ax[0].yaxis.set_label_position("right")
|
|
ax[0].set_ylabel("Share Price ($)")
|
|
ax[0].grid(axis="y", color="gainsboro", linestyle="-", linewidth=0.5)
|
|
|
|
ax[0].spines["top"].set_visible(False)
|
|
ax[0].spines["left"].set_visible(False)
|
|
ax[1].bar(
|
|
df.index, df.Volume / 1_000_000, color=bar_colors, alpha=0.8, width=bar_width
|
|
)
|
|
ax[1].set_xlim(df.index[0], df.index[-1])
|
|
ax[1].yaxis.tick_right()
|
|
ax[1].yaxis.set_label_position("right")
|
|
ax[1].set_ylabel("Volume (1M)")
|
|
ax[1].grid(axis="y", color="gainsboro", linestyle="-", linewidth=0.5)
|
|
ax[1].spines["top"].set_visible(False)
|
|
ax[1].spines["left"].set_visible(False)
|
|
ax[1].set_xlabel("Time")
|
|
fig.suptitle(
|
|
symbol + " " + title_string,
|
|
size=20,
|
|
x=0.15,
|
|
y=0.95,
|
|
fontfamily="serif",
|
|
fontstyle="italic",
|
|
)
|
|
if gtff.USE_ION:
|
|
plt.ion()
|
|
fig.tight_layout(pad=2)
|
|
plt.setp(ax[1].get_xticklabels(), rotation=20, horizontalalignment="right")
|
|
|
|
plt.show()
|
|
print("")
|
|
|
|
|
|
def load_ticker(
|
|
ticker: str, start_date: Union[str, datetime], end_date: Union[str, datetime] = ""
|
|
) -> pd.DataFrame:
|
|
"""Loads a ticker data from Yahoo Finance, adds a data index column data_id and Open-Close High/Low columns.
|
|
|
|
Parameters
|
|
----------
|
|
ticker : str
|
|
The stock ticker.
|
|
start_date : Union[str,datetime]
|
|
Start date to load stock ticker data formatted YYYY-MM-DD.
|
|
end_date : Union[str,datetime]
|
|
End date to load stock ticker data formatted YYYY-MM-DD.
|
|
|
|
Returns
|
|
-------
|
|
DataFrame
|
|
A Panda's data frame with columns Open, High, Low, Close, Adj Close, Volume, date_id, OC-High, OC-Low.
|
|
"""
|
|
if end_date:
|
|
df_data = yf.download(ticker, start=start_date, end=end_date, progress=False)
|
|
else:
|
|
df_data = yf.download(ticker, start=start_date, progress=False)
|
|
|
|
df_data["date_id"] = (df_data.index.date - df_data.index.date.min()).astype(
|
|
"timedelta64[D]"
|
|
)
|
|
df_data["date_id"] = df_data["date_id"].dt.days + 1
|
|
|
|
df_data["OC_High"] = df_data[["Open", "Close"]].max(axis=1)
|
|
df_data["OC_Low"] = df_data[["Open", "Close"]].min(axis=1)
|
|
|
|
return df_data
|
|
|
|
|
|
def process_candle(df_data: pd.DataFrame) -> pd.DataFrame:
|
|
"""Process DataFrame into candle style plot
|
|
|
|
Parameters
|
|
----------
|
|
df_data : DataFrame
|
|
Stock dataframe.
|
|
|
|
Returns
|
|
-------
|
|
DataFrame
|
|
A Panda's data frame with columns Open, High, Low, Close, Adj Close, Volume, date_id, OC-High, OC-Low.
|
|
"""
|
|
df_data["date_id"] = (df_data.index.date - df_data.index.date.min()).astype(
|
|
"timedelta64[D]"
|
|
)
|
|
df_data["date_id"] = df_data["date_id"].dt.days + 1
|
|
|
|
df_data["OC_High"] = df_data[["Open", "Close"]].max(axis=1)
|
|
df_data["OC_Low"] = df_data[["Open", "Close"]].min(axis=1)
|
|
|
|
return df_data
|
|
|
|
|
|
def find_trendline(
|
|
df_data: pd.DataFrame, y_key: str, high_low: str = "high"
|
|
) -> pd.DataFrame:
|
|
"""Attempts to find a trend line based on y_key column from a given stock ticker data frame.
|
|
|
|
Parameters
|
|
----------
|
|
df_data : DataFrame
|
|
The stock ticker data frame with at least date_id, y_key columns.
|
|
|
|
y_key : str
|
|
Column name to base the trend line on.
|
|
|
|
high_low: str, optional
|
|
Either "high" or "low". High is the default.
|
|
|
|
Returns
|
|
-------
|
|
DataFrame
|
|
If a trend is successfully found,
|
|
An updated Panda's data frame with a trend data {y_key}_trend column.
|
|
If no trend was found,
|
|
An original Panda's data frame
|
|
"""
|
|
|
|
for iteration in [3, 4, 5, 6, 7]:
|
|
df_temp = df_data.copy()
|
|
while len(df_temp) > iteration:
|
|
reg = stats.linregress(
|
|
x=df_temp["date_id"],
|
|
y=df_temp[y_key],
|
|
)
|
|
|
|
if high_low == "high":
|
|
df_temp = df_temp.loc[
|
|
df_temp[y_key] > reg[0] * df_temp["date_id"] + reg[1]
|
|
]
|
|
else:
|
|
df_temp = df_temp.loc[
|
|
df_temp[y_key] < reg[0] * df_temp["date_id"] + reg[1]
|
|
]
|
|
|
|
if len(df_temp) > 1:
|
|
break
|
|
|
|
if len(df_temp) == 1:
|
|
return df_data
|
|
|
|
reg = stats.linregress(
|
|
x=df_temp["date_id"],
|
|
y=df_temp[y_key],
|
|
)
|
|
|
|
df_data[f"{y_key}_trend"] = reg[0] * df_data["date_id"] + reg[1]
|
|
|
|
return df_data
|