Added a Historical Beta Regression Plot (#2145)

* beta function initial commit

* Used pct returns instead of raw returns

* beta function initial commit

* Used pct returns instead of raw returns

* Moved beta from stocks/ca to stocks/qa

* Updated tests for stocks/qa/beta

* Added hugo docs page for stocks/qa/beta

* Updated beta docstrings and added graceful exit on bad ref ticker
This commit is contained in:
Alex Willis
2022-07-26 08:23:08 -04:00
committed by GitHub
parent f3a6a11788
commit a768e545a8
10 changed files with 190 additions and 0 deletions

1
i18n/en.yml vendored
View File

@@ -470,6 +470,7 @@ en:
stocks/qa/decompose: decomposition in cyclic-trend, season, and residuals of prices
stocks/qa/cusum: detects abrupt changes using cumulative sum algorithm of prices
stocks/qa/capm: capital asset pricing model
stocks/qa/beta: display beta w.r.t to any reference ticker
stocks/pred/load: load new ticker
stocks/pred/pick: pick target column for analysis
stocks/pred/_ticker: Ticker

View File

@@ -0,0 +1,63 @@
import pandas as pd
from scipy import stats
from openbb_terminal.stocks import stocks_helper
def beta_model(
stock_ticker: str,
ref_ticker: str,
stock: pd.DataFrame = None,
ref: pd.DataFrame = None,
) -> tuple[pd.Series, pd.Series, float, float]:
"""Calculate beta for a ticker and a reference ticker.
Parameters
----------
stock_ticker : str
A ticker to calculate beta for
ref_ticker : str
A reference ticker for the beta calculation (default in terminal is SPY)
stock : pd.DataFrame
stock_ticker price data
ref : pd.DataFrame
ref_ticker price data
Returns
-------
sr : pd.Series
stock_ticker close-to-close returns
rr : pd.Series
ref_ticker close-to-close returns
beta : float
alpha : float
"""
if stock is None:
stock = stocks_helper.load(stock_ticker)
else:
# TODO: When loaded in the stocks menu, the stock df columns are all
# lowercase but when loaded via stocks_helper.load(ticker) they start
# with an uppercase char. This should be consistent.
stock = stock.rename({"close": "Close"}, axis=1)
if ref is None:
ref = stocks_helper.load(ref_ticker)
if ref.empty:
raise Exception("Invalid ref ticker")
else:
ref = ref.rename({"close": "Close"}, axis=1)
# join returns
sr = 100 * stock["Close"].pct_change().to_frame()
sr = sr.rename({"Close": "Stock Pct Ret"}, axis=1)
rr = 100 * ref["Close"].pct_change().to_frame()
rr = rr.rename({"Close": "Ref Pct Ret"}, axis=1)
df = sr.merge(rr, how="outer", left_index=True, right_index=True)
df = df.dropna()
sr = df["Stock Pct Ret"].tolist()
rr = df["Ref Pct Ret"].tolist()
# compute lin reg
model = stats.linregress(rr, sr)
beta = model.slope
alpha = model.intercept
return sr, rr, beta, alpha

View File

@@ -0,0 +1,54 @@
"""Beta view"""
__docformat__ = "numpy"
import logging
import matplotlib.pyplot as plt
import pandas as pd
from openbb_terminal.decorators import log_start_end
from openbb_terminal.rich_config import console
from openbb_terminal.stocks.quantitative_analysis.beta_model import beta_model
logger = logging.getLogger(__name__)
@log_start_end(log=logger)
def beta_view(
stock_ticker: str,
ref_ticker: str,
stock: pd.DataFrame = None,
ref: pd.DataFrame = None,
) -> None:
"""Display the beta scatterplot + linear regression.
Parameters
----------
stock_ticker : str
A ticker to calculate beta for
ref_ticker : str
A reference ticker for the beta calculation (default in terminal is SPY)
stock : pd.DataFrame
stock_ticker price data
ref : pd.DataFrame
ref_ticker price data
"""
try:
sr, rr, beta, alpha = beta_model(stock_ticker, ref_ticker, stock, ref)
except Exception as e:
if str(e) == "Invalid ref ticker":
console.print(str(e) + "\n")
return
raise e
fig, ax = plt.subplots()
ax.scatter(rr, sr) # plot returns
ax.plot(ax.get_xlim(), [x * beta + alpha for x in ax.get_xlim()]) # plot lin reg
ax.set(
xlabel=f"{ref_ticker} Returns (%)",
ylabel=f"{stock_ticker} Returns (%)",
title=f"Beta of {stock_ticker} with respect to {ref_ticker}",
)
beta_text = f"Raw Beta={round(beta, 2)}\nAlpha={round(alpha, 2)}"
ax.text(0.9, 0.1, beta_text, horizontalalignment="right", transform=ax.transAxes)
fig.show()
console.print()

View File

@@ -23,6 +23,7 @@ from openbb_terminal.helper_funcs import (
from openbb_terminal.menu import session
from openbb_terminal.parent_classes import StockBaseController
from openbb_terminal.rich_config import console, MenuText
from openbb_terminal.stocks.quantitative_analysis.beta_view import beta_view
from openbb_terminal.stocks.quantitative_analysis.factors_view import capm_view
# pylint: disable=C0302
@@ -56,6 +57,7 @@ class QaController(StockBaseController):
"goodness",
"unitroot",
"capm",
"beta",
"var",
"es",
"sh",
@@ -148,6 +150,7 @@ class QaController(StockBaseController):
mt.add_cmd("decompose")
mt.add_cmd("cusum")
mt.add_cmd("capm")
mt.add_cmd("beta")
console.print(text=mt.menu_text, menu="Stocks - Quantitative Analysis")
def custom_reset(self):
@@ -752,6 +755,34 @@ class QaController(StockBaseController):
if ns_parser:
capm_view(self.ticker)
@log_start_end(log=logger)
def call_beta(self, other_args: List[str]):
"""Call the beta command on loaded ticker"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="beta",
description="""
Displays a scatter plot demonstrating the beta of two stocks or ETFs.
""",
)
parser.add_argument(
"-r",
"--ref",
action="store",
dest="ref",
type=str,
default="SPY",
help="""
Reference ticker used for beta calculation.
""",
)
ns_parser = self.parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
beta_view(self.ticker, ns_parser.ref, stock=self.stock)
@log_start_end(log=logger)
def call_var(self, other_args: List[str]):
"""Process var command"""

View File

@@ -27,6 +27,7 @@ raw
decompose
cusum
capm
beta
var
es
sh

View File

@@ -0,0 +1 @@
[[100.0, 50.0, 33.33333333333333, 25.0, 19.999999999999996, 16.666666666666675, 14.28571428571428, 12.5, 11.111111111111116], [50.0, 66.66666666666667, 39.99999999999999, 0.0, 28.57142857142858, 122.22222222222223, -110.00000000000001, 150.0, -100.0], 0.05941184834947979, 29.79941672703988]

View File

@@ -0,0 +1,19 @@
# IMPORTATION STANDARD
# IMPORTATION THIRDPARTY
import pandas as pd
import pytest
# IMPORTATION INTERNAL
from openbb_terminal.stocks.quantitative_analysis.beta_model import beta_model
@pytest.mark.vcr
def test_beta_model(recorder):
stock = pd.DataFrame.from_dict({"close": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]})
ref = pd.DataFrame.from_dict({"close": [2, 3, 5, 7, 7, 9, 20, -2, -5, 0]})
result_tuple = beta_model(
stock_ticker="TSLA", ref_ticker="XLK", stock=stock, ref=ref
)
recorder.capture(result_tuple)

View File

@@ -507,6 +507,13 @@ def test_call_func_expect_queue(expected_queue, queue, func):
[QA_CONTROLLER.ticker],
dict(),
),
(
"call_beta",
["--ref=XLK"],
"beta_view",
[QA_CONTROLLER.ticker, "XLK"],
{"stock": QA_CONTROLLER.stock},
),
],
)
def test_call_func(

View File

@@ -32,4 +32,5 @@ Other:
decompose decomposition in cyclic-trend, season, and residuals of prices
cusum detects abrupt changes using cumulative sum algorithm of prices
capm capital asset pricing model
beta display beta w.r.t to any reference ticker

View File

@@ -0,0 +1,12 @@
```text
usage: beta [--ref REF_TICKER] [-h]
```
Displays scatter plot of returns of a stock against a reference ticker and calculates the line of best fit through them to compute alpha and beta.
```
optional arguments:
-r, --ref Specify the reference ticker to use (default: SPY)
-h, --help show this help message (default: False)
```
![beta](https://user-images.githubusercontent.com/62662248/180587175-5e548915-0d3e-44cf-bbff-3655d51e1d64.png)