add rolling metrics to eda (#677)

* add rolling metrics to eda

* .

* try fix incompatible type error

* overlay median & quantile plotting
This commit is contained in:
devwolff
2021-08-18 08:14:29 +10:00
committed by GitHub
parent 92c24d4fea
commit ed4c5877a8
3 changed files with 419 additions and 13 deletions

View File

@@ -0,0 +1,365 @@
"""Rolling Statistics"""
__docformat__ = "numpy"
import argparse
from typing import List
import matplotlib.pyplot as plt
import pandas_ta as ta
import pandas as pd
from pandas.plotting import register_matplotlib_converters
from gamestonk_terminal.helper_funcs import (
check_positive,
check_proportion_range,
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 spread(
other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame
):
"""Standard Deviation & Variance
Parameters
----------
other_args:List[str]
Argparse arguments
s_ticker: str
Ticker
s_interval: str
Stock time interval
df_stock: pd.DataFrame
Dataframe of prices
"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="spread",
description="""
""",
)
parser.add_argument(
"-l",
"--length",
action="store",
dest="n_length",
type=check_positive,
default=14,
help="length",
)
parser.add_argument(
"-o",
"--offset",
action="store",
dest="n_offset",
type=check_positive,
default=0,
help="offset",
)
try:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
# Daily
if s_interval == "1440min":
df_ta = ta.stdev(
close=df_stock["Adj Close"],
length=ns_parser.n_length,
offset=ns_parser.n_offset,
).dropna()
df_ta_ = ta.variance(
close=df_stock["Adj Close"],
length=ns_parser.n_length,
offset=ns_parser.n_offset,
).dropna()
# Intraday
else:
df_ta = ta.stdev(
close=df_stock["Close"],
length=ns_parser.n_length,
offset=ns_parser.n_offset,
).dropna()
df_ta_ = ta.variance(
close=df_stock["Adj Close"],
length=ns_parser.n_length,
offset=ns_parser.n_offset,
).dropna()
fig, axes = plt.subplots(3, 1, figsize=plot_autoscale(), dpi=PLOT_DPI)
ax = axes[0]
ax.set_title(f"{s_ticker} Spread")
if s_interval == "1440min":
ax.plot(df_stock.index, df_stock["Adj Close"].values, "fuchsia", lw=1)
else:
ax.plot(df_stock.index, df_stock["Close"].values, "fuchsia", lw=1)
ax.set_xlim(df_stock.index[0], df_stock.index[-1])
ax.set_ylabel("Share Price ($)")
ax.yaxis.set_label_position("right")
ax.grid(b=True, which="major", color="#666666", linestyle="-")
ax.minorticks_on()
ax.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2)
ax1 = axes[1]
ax1.plot(df_ta.index, df_ta.values, "b", lw=1, label="stdev")
ax1.set_xlim(df_stock.index[0], df_stock.index[-1])
ax1.set_ylabel("Stdev")
ax1.yaxis.set_label_position("right")
ax1.grid(b=True, which="major", color="#666666", linestyle="-")
ax1.minorticks_on()
ax1.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2)
ax2 = axes[2]
ax2.plot(df_ta_.index, df_ta_.values, "g", lw=1, label="variance")
ax2.set_xlim(df_stock.index[0], df_stock.index[-1])
ax2.set_ylabel("Variance")
ax2.yaxis.set_label_position("right")
ax2.grid(b=True, which="major", color="#666666", linestyle="-")
ax2.minorticks_on()
ax2.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2)
if gtff.USE_ION:
plt.ion()
plt.gcf().autofmt_xdate()
fig.tight_layout(pad=1)
plt.show()
print("")
except Exception as e:
print(e, "\n")
def quantile(
other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame
):
"""Overlay Median & Quantile
Parameters
----------
other_args: List[str]
Argparse arguments
s_ticker: str
Ticker
s_interval: str
Data interval
df_stock: pd.DataFrame
Dataframe of dates and prices
"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="quantile",
description="""
The quantiles are values which divide the distribution such that
there is a given proportion of observations below the quantile.
For example, the median is a quantile. The median is the central
value of the distribution, such that half the points are less than
or equal to it and half are greater than or equal to it.
By default, q is set at 0.5, which effectively is median. Change q to
get the desired quantile (0<q<1).
""",
)
parser.add_argument(
"-l",
"--length",
action="store",
dest="n_length",
type=check_positive,
default=14,
help="length",
)
parser.add_argument(
"-o",
"--offset",
action="store",
dest="n_offset",
type=check_positive,
default=0,
help="offset",
)
parser.add_argument(
"-q",
"--quantile",
action="store",
dest="f_quantile",
type=check_proportion_range,
default=0.5,
help="quantile",
)
try:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
if s_interval == "1440min":
plt.plot(df_stock.index, df_stock["Adj Close"].values, color="fuchsia")
else:
plt.plot(df_stock.index, df_stock["Close"].values, color="fuchsia")
ax.set_title(f"{s_ticker} Median & Quantile")
if s_interval == "1440min":
df_ta_ = ta.median(
close=df_stock["Adj Close"],
length=ns_parser.n_length,
offset=ns_parser.n_offset,
).dropna()
df_ta = ta.quantile(
df_stock["Adj Close"],
length=ns_parser.n_length,
offset=ns_parser.n_offset,
q=ns_parser.f_quantile,
).dropna()
else:
df_ta_ = ta.median(
close=df_stock["Adj Close"],
length=ns_parser.n_length,
offset=ns_parser.n_offset,
).dropna()
df_ta = ta.quantile(
df_stock["Close"],
ns_parser.n_length,
offset=ns_parser.n_offset,
q=ns_parser.f_quantile,
).dropna()
plt.plot(df_ta_.index, df_ta_.values, "g", lw=1, label="median")
plt.plot(df_ta.index, df_ta.values, "b", lw=1, label="quantile")
plt.title(f"Median & Quantile on {s_ticker}")
plt.xlim(df_stock.index[0], df_stock.index[-1])
plt.xlabel("Time")
plt.ylabel(f"{s_ticker} Price ($)")
plt.grid(b=True, which="major", color="#666666", linestyle="-")
plt.minorticks_on()
plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2)
if gtff.USE_ION:
plt.ion()
plt.gcf().autofmt_xdate()
fig.tight_layout(pad=1)
plt.legend()
plt.show()
print("")
except Exception as e:
print(e, "\n")
def skew(other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame):
"""Skewness Indicator
Parameters
----------
other_args:List[str]
Argparse arguments
s_ticker: str
Ticker
s_interval: str
Stock time interval
df_stock: pd.DataFrame
Dataframe of prices
"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="skew",
description="""
Skewness is a measure of asymmetry or distortion of symmetric
distribution. It measures the deviation of the given distribution
of a random variable from a symmetric distribution, such as normal
distribution. A normal distribution is without any skewness, as it is
symmetrical on both sides. Hence, a curve is regarded as skewed if
it is shifted towards the right or the left.
""",
)
parser.add_argument(
"-l",
"--length",
action="store",
dest="n_length",
type=check_positive,
default=14,
help="length",
)
parser.add_argument(
"-o",
"--offset",
action="store",
dest="n_offset",
type=check_positive,
default=0,
help="offset",
)
try:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
# Daily
if s_interval == "1440min":
df_ta = ta.skew(
close=df_stock["Adj Close"],
length=ns_parser.n_length,
offset=ns_parser.n_offset,
).dropna()
# Intraday
else:
df_ta = ta.skew(
close=df_stock["Close"],
length=ns_parser.n_length,
offset=ns_parser.n_offset,
).dropna()
fig, axes = plt.subplots(2, 1, figsize=plot_autoscale(), dpi=PLOT_DPI)
ax = axes[0]
ax.set_title(f"{s_ticker} Skewness Indicator")
if s_interval == "1440min":
ax.plot(df_stock.index, df_stock["Adj Close"].values, "fuchsia", lw=1)
else:
ax.plot(df_stock.index, df_stock["Close"].values, "fuchsia", lw=1)
ax.set_xlim(df_stock.index[0], df_stock.index[-1])
ax.set_ylabel("Share Price ($)")
ax.grid(b=True, which="major", color="#666666", linestyle="-")
ax.minorticks_on()
ax.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2)
ax2 = axes[1]
ax2.plot(df_ta.index, df_ta.values, "b", lw=2, label="skew")
ax2.set_xlim(df_stock.index[0], df_stock.index[-1])
ax2.grid(b=True, which="major", color="#666666", linestyle="-")
ax2.minorticks_on()
ax2.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2)
if gtff.USE_ION:
plt.ion()
plt.gcf().autofmt_xdate()
fig.tight_layout(pad=1)
plt.legend()
plt.show()
print("")
except Exception as e:
print(e, "\n")

View File

@@ -1,30 +1,26 @@
"""Helper functions"""
__docformat__ = "numpy"
import argparse
from typing import List
from datetime import datetime, timedelta, time as Time
import os
import random
import re
import sys
from datetime import datetime
from datetime import time as Time
from datetime import timedelta
from typing import List
import pandas as pd
from pytz import timezone
from prettytable import PrettyTable
import iso8601
import matplotlib
import matplotlib.pyplot as plt
import pandas as pd
import pandas.io.formats.format
from colorama import Fore, Style
from holidays import US as holidaysUS
from colorama import Fore, Style
from pandas._config.config import get_option
from pandas.plotting import register_matplotlib_converters
from prettytable import PrettyTable
from pytz import timezone
import pandas.io.formats.format
from screeninfo import get_monitors
from gamestonk_terminal import config_plot as cfgPlot
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal import config_plot as cfgPlot
register_matplotlib_converters()
if cfgPlot.BACKEND is not None:
@@ -120,6 +116,30 @@ def check_positive(value) -> int:
return ivalue
def check_proportion_range(num) -> float:
"""Checks if float is between 0 and 1. If so, return it.
Parameters
----------
num: float
Input float
Returns
-------
num: float
Input number if conditions are met
Raises
-------
argparse.ArgumentTypeError
Input number not between min and max values
"""
num = float(num)
maxi = 1.0
mini = 0.0
if num < mini or num > maxi:
raise argparse.ArgumentTypeError("Value must be between 0 and 1")
return num
def valid_date(s: str) -> datetime:
"""Argparse type to check date is in valid format"""
try:

View File

@@ -8,7 +8,7 @@ from datetime import datetime
import pandas as pd
from matplotlib import pyplot as plt
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal.common.exploratory_data_analysis import eda_api
from gamestonk_terminal.common.exploratory_data_analysis import eda_api, rolling
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import get_flair
from gamestonk_terminal.menu import session
@@ -33,6 +33,9 @@ class EdaController:
"decompose",
"cusum",
"acf",
"spread",
"quantile",
"skew",
]
def __init__(
@@ -79,7 +82,13 @@ class EdaController:
print(" cdf cumulative distribution function")
print(" bwy box and whisker yearly plot")
print(" bwm box and whisker monthly plot")
print("")
print("Rolling Metrics")
print(" rolling rolling mean and std deviation")
print(" spread variance and std deviation")
print(" quantile median and quantile")
print(" skew skewness of distribution")
print("")
print(" decompose decomposition in cyclic-trend, season, and residuals")
print(" cusum detects abrupt changes using cumulative sum algorithm")
print(" acf (partial) auto-correlation function differentials")
@@ -168,6 +177,18 @@ class EdaController:
"""Process acf command"""
eda_api.acf(other_args, self.ticker, self.stock, self.start)
def call_spread(self, other_args: List[str]):
"""Process spread command"""
rolling.spread(other_args, self.ticker, self.interval, self.stock)
def call_quantile(self, other_args: List[str]):
"""Process quantile command"""
rolling.quantile(other_args, self.ticker, self.interval, self.stock)
def call_skew(self, other_args: List[str]):
"""Process skew command"""
rolling.skew(other_args, self.ticker, self.interval, self.stock)
def menu(ticker: str, start: datetime, interval: str, stock: pd.DataFrame):
"""Statistics Menu"""