diff --git a/README.md b/README.md
index 9ae1244d..0f1b8cc1 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
-
+
@@ -169,6 +169,7 @@ As it is common for open-source projects, there are several ways to get hold of
- quandl>=3.4.5
- yfinance>=0.1.43
- scipy>=1.2.0
+ - scikit-learn>=1.3.0
### From PyPI
*FinQuant* can be obtained from PyPI
@@ -253,7 +254,9 @@ look at the examples provided in `./example`.
- Value at Risk,
- Sharpe Ratio,
- Sortino Ratio,
- - Beta parameter.
+ - Treynor Ratio,
+ - Beta parameter,
+ - R squared coefficient.
It also shows how to extract individual stocks from the given portfolio. Moreover it shows how to compute and visualise:
- the different Returns provided by the module `finquant.returns`,
diff --git a/README.tex.md b/README.tex.md
index cdf10d64..6dd741fd 100644
--- a/README.tex.md
+++ b/README.tex.md
@@ -7,7 +7,7 @@
-
+
@@ -169,6 +169,7 @@ As it is common for open-source projects, there are several ways to get hold of
- quandl>=3.4.5
- yfinance>=0.1.43
- scipy>=1.2.0
+ - scikit-learn>=1.3.0
### From PyPI
*FinQuant* can be obtained from PyPI
@@ -253,7 +254,9 @@ look at the examples provided in `./example`.
- Value at Risk,
- Sharpe Ratio,
- Sortino Ratio,
- - Beta parameter.
+ - Treynor Ratio,
+ - Beta parameter,
+ - R squared coefficient.
It also shows how to extract individual stocks from the given portfolio. Moreover it shows how to compute and visualise:
- the different Returns provided by the module `finquant.returns`,
diff --git a/example/Example-Build-Portfolio-from-web.py b/example/Example-Build-Portfolio-from-web.py
index f2406c24..d0abe52f 100644
--- a/example/Example-Build-Portfolio-from-web.py
+++ b/example/Example-Build-Portfolio-from-web.py
@@ -66,7 +66,8 @@
# * `build_portfolio(names=names, data_api="yfinance")`
#
# In the below example we are using `yfinance` to download stock data. We specify the start and end date of the stock prices to be downloaded.
-# We also provide the optional parameter `market_index` to download the historical data of a market index. `FinQuant` can use them to calculate the beta parameter, measuring the portfolio's daily volatility compared to the market.
+# We also provide the optional parameter `market_index` to download the historical data of a market index.
+# `FinQuant` can use them to calculate the Treynor Ratio, beta parameter, and R squared coefficient, measuring the portfolio's daily volatility compared to the market.
#
diff --git a/finquant/portfolio.py b/finquant/portfolio.py
index ad840b72..9a570f53 100644
--- a/finquant/portfolio.py
+++ b/finquant/portfolio.py
@@ -23,7 +23,9 @@
- Value at Risk,
- Sharpe Ratio,
- Sortino Ratio,
+- Treynor Ratio (optional),
- Beta parameter (optional),
+- R squared coefficient (optional),
- skewness of the portfolio's stocks,
- Kurtosis of the portfolio's stocks,
- the portfolio's covariance matrix.
@@ -84,6 +86,7 @@
downside_risk,
sharpe_ratio,
sortino_ratio,
+ treynor_ratio,
value_at_risk,
weighted_mean,
weighted_std,
@@ -116,6 +119,7 @@ class Portfolio:
var: FLOAT
sharpe: FLOAT
sortino: FLOAT
+ treynor: Optional[FLOAT]
skew: pd.Series
kurtosis: pd.Series
__totalinvestment: NUMERIC
@@ -127,6 +131,8 @@ class Portfolio:
__market_index: Optional[Market]
beta_stocks: pd.DataFrame
beta: Optional[FLOAT]
+ rsquared_stocks: pd.DataFrame
+ rsquared: Optional[FLOAT]
def __init__(self) -> None:
"""Initiates ``Portfolio``."""
@@ -142,9 +148,14 @@ def __init__(self) -> None:
self.mc = None
# instance variable for Market class
self.__market_index = None
- # dataframe containing beta values of stocks
+ # Treynor Ratio of the portfolio
+ self.treynor = None
+ # dataframe containing beta parameters of stocks
self.beta_stocks = pd.DataFrame(index=["beta"])
self.beta = None
+ # dataframe containing rsquared coefficients of stocks
+ self.rsquared_stocks = pd.DataFrame(index=["rsquared"])
+ self.rsquared = None
@property
def totalinvestment(self) -> NUMERIC:
@@ -234,6 +245,13 @@ def add_stock(self, stock: Stock, defer_update: bool = False) -> None:
- ``skew``: Skewness of the portfolio's stocks
- ``kurtosis``: Kurtosis of the portfolio's stocks
+ If argument ``defer_update`` is ``True`` and ``__market_index`` is not ``None``,
+ the following instance variables are (re-)computed as well:
+
+ - ``beta``: Beta parameter of the portfolio
+ - ``rsquared``: R squared coefficient of the portfolio
+ - ``treynor``: Treynor Ratio of the portfolio
+
:param stock: An instance of the class ``Stock``.
:param defer_update: bool, if True instance variables are not (re-)computed at the end of this method.
"""
@@ -267,6 +285,10 @@ def _add_stock_data(self, stock: Stock) -> None:
beta_stock = stock.comp_beta(self.market_index.daily_returns)
# add beta of stock to portfolio's betas dataframe
self.beta_stocks[stock.name] = [beta_stock]
+ # compute R squared coefficient of stock
+ rsquared_stock = stock.comp_rsquared(self.market_index.daily_returns)
+ # add rsquared of stock to portfolio's R squared dataframe
+ self.rsquared_stocks[stock.name] = [rsquared_stock]
def _update(self) -> None:
# sanity check (only update values if none of the below is empty):
@@ -282,6 +304,8 @@ def _update(self) -> None:
self.kurtosis = self._comp_kurtosis()
if self.market_index is not None:
self.beta = self.comp_beta()
+ self.rsquared = self.comp_rsquared()
+ self.treynor = self.comp_treynor()
def get_stock(self, name: str) -> Stock:
"""Returns the instance of ``Stock`` with name ``name``.
@@ -458,6 +482,25 @@ def comp_beta(self) -> Optional[FLOAT]:
else:
return None
+ def comp_rsquared(self) -> Optional[FLOAT]:
+ """Compute and return the R squared coefficient of the portfolio.
+
+ :rtype: :py:data:`~.finquant.data_types.FLOAT`
+ :return: R squared coefficient of the portfolio
+ """
+
+ # compute the R squared coefficient of the portfolio
+ weights: pd.Series = self.comp_weights()
+ if weights.size == self.beta_stocks.size:
+ rsquared: FLOAT = weighted_mean(
+ self.rsquared_stocks.transpose()["rsquared"].values, weights
+ )
+
+ self.rsquared = rsquared
+ return rsquared
+ else:
+ return None
+
def comp_sortino(self) -> FLOAT:
"""Compute and return the Sortino Ratio of the portfolio
@@ -469,6 +512,19 @@ def comp_sortino(self) -> FLOAT:
self.expected_return, self.downside_risk, self.risk_free_rate
)
+ def comp_treynor(self) -> Optional[FLOAT]:
+ """Compute and return the Treynor Ratio of the portfolio.
+
+ :rtype: :py:data:`~.finquant.data_types.FLOAT`
+ :return: The Treynor Ratio of the portfolio.
+ """
+ # compute the Treynor Ratio of the portfolio
+ treynor: Optional[FLOAT] = treynor_ratio(
+ self.expected_return, self.beta, self.risk_free_rate
+ )
+ self.treynor = treynor
+ return treynor
+
def _comp_skew(self) -> pd.Series:
"""Computes and returns the skewness of the stocks in the portfolio."""
return self.data.skew()
@@ -731,7 +787,9 @@ def properties(self) -> None:
- Confidence level of VaR,
- Sharpe Ratio,
- Sortino Ratio,
+ - Treynor Ratio (optional),
- Beta (optional),
+ - R squared (optional),
- skewness,
- Kurtosis
@@ -755,8 +813,12 @@ def properties(self) -> None:
string += f"{self.var_confidence_level * 100:0.2f} %"
string += f"\nPortfolio Sharpe Ratio: {self.sharpe:0.3f}"
string += f"\nPortfolio Sortino Ratio: {self.sortino:0.3f}"
+ if self.treynor is not None:
+ string += f"\nPortfolio Treynor Ratio: {self.treynor:0.3f}"
if self.beta is not None:
string += f"\nPortfolio Beta: {self.beta:0.3f}"
+ if self.rsquared is not None:
+ string += f"\nPortfolio R squared: {self.rsquared:0.3f}"
string += "\n\nSkewness:"
string += "\n" + str(self.skew.to_frame().transpose())
string += "\n\nKurtosis:"
@@ -999,8 +1061,8 @@ def _build_portfolio_from_api(
if data is not provided by the user. Valid values:
- ``quandl`` (Python package/API to `Quandl`)
- ``yfinance`` (Python package formerly known as ``fix-yahoo-finance``)
- :param market_index: (optional) A string which determines the market index to be used for the
- computation of the beta parameter of the stocks, default: ``None``
+ :param market_index: (optional, default: ``None``) A string which determines the market index
+ to be used for the computation of the Trenor Ratio, beta parameter and the R squared of the portfolio.
:return: Instance of Portfolio which contains all the information requested by the user.
"""
@@ -1227,9 +1289,9 @@ def build_portfolio(**kwargs: Dict[str, Any]) -> Portfolio:
- ``quandl`` (Python package/API to `Quandl`)
- ``yfinance`` (Python package formerly known as ``fix-yahoo-finance``)
- :param market_index: (optional) string which determines the
- market index to be used for the computation of the beta parameter of the stocks,
- default: ``None``.
+ :param market_index: (optional) A string (default: ``None``) which determines the
+ market index to be used for the computation of the Treynor ratio, beta parameter
+ and the R squared coefficient of the portflio.
:return: Instance of ``Portfolio`` which contains all the information requested by the user.
diff --git a/finquant/quants.py b/finquant/quants.py
index 99bddaaf..30727a1e 100644
--- a/finquant/quants.py
+++ b/finquant/quants.py
@@ -4,7 +4,7 @@
"""
-from typing import Tuple
+from typing import Optional, Tuple
import numpy as np
import pandas as pd
@@ -115,6 +115,37 @@ def sortino_ratio(
return (exp_return - risk_free_rate) / float(downs_risk)
+def treynor_ratio(
+ exp_return: FLOAT, beta: Optional[FLOAT], risk_free_rate: FLOAT = 0.005
+) -> Optional[FLOAT]:
+ """Computes the Treynor Ratio.
+
+ :param exp_return: Expected Return of a portfolio
+ :type exp_return: :py:data:`~.finquant.data_types.FLOAT`
+
+ :param beta: Beta parameter of a portfolio
+ :type beta: :py:data:`~.finquant.data_types.FLOAT`
+
+ :param risk_free_rate: Risk free rate
+ :type risk_free_rate: :py:data:`~.finquant.data_types.FLOAT`, default: 0.005
+
+ :rtype: :py:data:`~.finquant.data_types.FLOAT`
+ :return: Treynor Ratio as a floating point number:
+ ``(exp_return - risk_free_rate) / beta``
+ """
+ # Type validations:
+ type_validation(
+ expected_return=exp_return,
+ beta_parameter=beta,
+ risk_free_rate=risk_free_rate,
+ )
+ if beta is None:
+ return None
+ else:
+ res_treynor_ratio: FLOAT = (exp_return - risk_free_rate) / beta
+ return res_treynor_ratio
+
+
def downside_risk(
data: pd.DataFrame, weights: ARRAY_OR_SERIES[FLOAT], risk_free_rate: FLOAT = 0.005
) -> FLOAT:
diff --git a/finquant/stock.py b/finquant/stock.py
index bd3113c7..b512a835 100644
--- a/finquant/stock.py
+++ b/finquant/stock.py
@@ -16,7 +16,7 @@
The ``Stock`` class computes various quantities related to the stock or fund, such as expected return,
volatility, skewness, and kurtosis. It also provides functionality to calculate the beta parameter
-of the stock using the CAPM (Capital Asset Pricing Model).
+using the CAPM (Capital Asset Pricing Model) and the R squared value of the stock .
The ``Stock`` class inherits from the ``Asset`` class in ``finquant.asset``, which provides common
functionality and attributes for financial assets.
@@ -27,6 +27,7 @@
import numpy as np
import pandas as pd
+from sklearn.metrics import r2_score
from finquant.asset import Asset
from finquant.data_types import FLOAT
@@ -44,14 +45,15 @@ class Stock(Asset):
It requires investment information and historical price data for the stock to initialize an instance.
In addition to the attributes inherited from the ``Asset`` class, the ``Stock`` class provides
- a method to compute the beta parameter specific to stocks in a portfolio when compared to
- the market index.
+ a method to compute the beta parameter and one to compute the R squared coefficient
+ specific to stocks in a portfolio when compared to the market index.
"""
# Attributes:
investmentinfo: pd.DataFrame
beta: Optional[FLOAT]
+ rsquared: Optional[FLOAT]
def __init__(self, investmentinfo: pd.DataFrame, data: pd.Series) -> None:
"""
@@ -63,6 +65,8 @@ def __init__(self, investmentinfo: pd.DataFrame, data: pd.Series) -> None:
super().__init__(data, self.name, asset_type="Stock")
# beta parameter of stock (CAPM)
self.beta = None
+ # R squared coefficient of stock
+ self.rsquared = None
def comp_beta(self, market_daily_returns: pd.Series) -> FLOAT:
"""Computes and returns the Beta parameter of the stock.
@@ -83,10 +87,30 @@ def comp_beta(self, market_daily_returns: pd.Series) -> FLOAT:
self.beta = beta
return beta
+ def comp_rsquared(self, market_daily_returns: pd.Series) -> FLOAT:
+ """Computes and returns the R squared coefficient of the stock.
+
+ :param market_daily_returns: Daily returns of the market index.
+
+ :rtype: :py:data:`~.finquant.data_types.FLOAT`
+ :return: R squared coefficient of the stock
+ """
+ # Type validations:
+ type_validation(market_daily_returns=market_daily_returns)
+
+ rsquared = float(
+ r2_score(
+ market_daily_returns.to_frame()[market_daily_returns.name],
+ self.comp_daily_returns(),
+ )
+ )
+ self.rsquared = rsquared
+ return rsquared
+
def properties(self) -> None:
"""Nicely prints out the properties of the stock: Expected Return,
- Volatility, Beta (optional), Skewness, Kurtosis as well as the ``Allocation`` (and other
- information provided in investmentinfo.)
+ Volatility, Beta (optional), R squared (optional), Skewness, Kurtosis as well as the ``Allocation``
+ (and other information provided in investmentinfo.)
"""
# nicely printing out information and quantities of the stock
string = "-" * 50
@@ -95,6 +119,8 @@ def properties(self) -> None:
string += f"\nVolatility: {self.volatility:0.3f}"
if self.beta is not None:
string += f"\n{self.asset_type} Beta: {self.beta:0.3f}"
+ if self.rsquared is not None:
+ string += f"\n{self.asset_type} R squared: {self.rsquared:0.3f}"
string += f"\nSkewness: {self.skew:0.5f}"
string += f"\nKurtosis: {self.kurtosis:0.5f}"
string += "\nInformation:"
diff --git a/finquant/type_utilities.py b/finquant/type_utilities.py
index 313b4c32..25d86fd7 100644
--- a/finquant/type_utilities.py
+++ b/finquant/type_utilities.py
@@ -145,6 +145,7 @@ def _check_empty_data(arg_name: str, arg_values: Any) -> None:
"mu": ((float, np.floating), None),
"sigma": ((float, np.floating), None),
"conf_level": ((float, np.floating), None),
+ "beta_parameter": ((float, np.floating), None),
# INTs:
"freq": ((int, np.integer), None),
"span": ((int, np.integer), None),
diff --git a/requirements.txt b/requirements.txt
index 58c18e68..3216f4da 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,4 +3,5 @@ scipy>=1.2.0
pandas>=2.0
matplotlib>=3.0
quandl>=3.4.5
-yfinance>=0.1.43
\ No newline at end of file
+yfinance>=0.1.43
+scikit-learn>=1.3.0
\ No newline at end of file
diff --git a/tests/test_market.py b/tests/test_market.py
index b58c5578..cccb2c40 100644
--- a/tests/test_market.py
+++ b/tests/test_market.py
@@ -40,3 +40,5 @@ def test_Market():
assert isinstance(pf.market_index, Market)
assert pf.market_index.name == "^GSPC"
assert pf.beta is not None
+ assert pf.rsquared is not None
+ assert pf.treynor is not None
diff --git a/tests/test_quants.py b/tests/test_quants.py
index 1d39763c..f7916941 100644
--- a/tests/test_quants.py
+++ b/tests/test_quants.py
@@ -9,6 +9,7 @@
downside_risk,
sharpe_ratio,
sortino_ratio,
+ treynor_ratio,
value_at_risk,
weighted_mean,
weighted_std,
@@ -44,6 +45,11 @@ def test_sortino_ratio():
assert sortino_ratio(0.005, 8.5, 0.005) == 0.0
+def test_treynor_ratio():
+ assert treynor_ratio(0.2, 0.9, 0.002) == 0.22
+ assert treynor_ratio(0.005, 0.92, 0.005) == 0.0
+
+
def test_value_at_risk():
assert abs(value_at_risk(1e2, 0.5, 0.25, 0.95) - 91.12) <= 1e-1
assert abs(value_at_risk(1e3, 0.8, 0.5, 0.99) - 1963.17) <= 1e-1
diff --git a/version b/version
index d4576a1d..7ad36c63 100644
--- a/version
+++ b/version
@@ -1,2 +1,2 @@
-version=0.6.2
-release=0.6.2
+version=0.7.0
+release=0.7.0