mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 12:40:35 +00:00
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:
1
i18n/en.yml
vendored
1
i18n/en.yml
vendored
@@ -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
|
||||
|
||||
63
openbb_terminal/stocks/quantitative_analysis/beta_model.py
Normal file
63
openbb_terminal/stocks/quantitative_analysis/beta_model.py
Normal 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
|
||||
54
openbb_terminal/stocks/quantitative_analysis/beta_view.py
Normal file
54
openbb_terminal/stocks/quantitative_analysis/beta_view.py
Normal 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()
|
||||
@@ -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"""
|
||||
|
||||
1
scripts/test_stocks_qa.openbb
vendored
1
scripts/test_stocks_qa.openbb
vendored
@@ -27,6 +27,7 @@ raw
|
||||
decompose
|
||||
cusum
|
||||
capm
|
||||
beta
|
||||
var
|
||||
es
|
||||
sh
|
||||
|
||||
1
tests/openbb_terminal/stocks/quantitative_analysis/json/test_beta_model/test_beta_model.json
vendored
Normal file
1
tests/openbb_terminal/stocks/quantitative_analysis/json/test_beta_model/test_beta_model.json
vendored
Normal 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]
|
||||
@@ -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)
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
12
website/content/terminal/common/qa/beta/_index.md
vendored
Normal file
12
website/content/terminal/common/qa/beta/_index.md
vendored
Normal 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)
|
||||
```
|
||||

|
||||
Reference in New Issue
Block a user