From 184c014fed6c35c26710d2ed22479d6ed523170e Mon Sep 17 00:00:00 2001 From: Frank Milthaler Date: Wed, 19 Jul 2023 07:35:44 +0200 Subject: [PATCH] Refactor/asset stock market index (#107) Refactoring the code: Instead of having almost identical classes for Stock and Market, we now introduce a parent class Asset for both. --- README.md | 2 +- README.tex.md | 2 +- finquant/asset.py | 122 +++++++++++++++++++++++++++++++++++++++++++ finquant/market.py | 94 ++++++++++------------------------ finquant/stock.py | 125 ++++++++++++++++----------------------------- version | 4 +- 6 files changed, 197 insertions(+), 152 deletions(-) create mode 100644 finquant/asset.py diff --git a/README.md b/README.md index 318af008..ab9e0606 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ pypi - pypi + pypi GitHub Actions diff --git a/README.tex.md b/README.tex.md index e1c79e7f..c4c956a5 100644 --- a/README.tex.md +++ b/README.tex.md @@ -7,7 +7,7 @@ pypi - pypi + pypi GitHub Actions diff --git a/finquant/asset.py b/finquant/asset.py new file mode 100644 index 00000000..7baba64b --- /dev/null +++ b/finquant/asset.py @@ -0,0 +1,122 @@ +""" +This module provides a public class ``Asset`` that represents a generic financial asset. +It serves as the parent class for specialized asset classes like ``Stock`` and ``Market`` +in the finquant library. + +An asset is characterized by its historical price data, from which various quantities +such as expected return, volatility, skewness, and kurtosis can be computed. The ``Asset`` +class provides common functionality and attributes that are shared among different types +of assets. + +The specialized asset classes, ``Stock`` and ``Market``, inherit from the ``Asset`` class +and add specific functionality tailored to their respective types. + +""" + + +import numpy as np +import pandas as pd + +from finquant.returns import daily_returns, historical_mean_return + + +class Asset: + """ + Parent class representing a generic financial asset. + + :param ``data``: Historical price data of the asset as a ``pandas.Series``. + :param ``name``: Name of the asset. + :param ``asset_type``: Type of the asset (e.g., "Stock" or "Market index"). + + The ``Asset`` class provides common functionality and attributes for financial assets. + It represents a generic asset and serves as the parent class for specialized asset classes. + + Attributes: + - ``data`` (``pandas.Series``): Historical price data of the asset. + - ``name`` (``str``): Name of the asset. + - ``asset_type`` (``str``): Type of the asset (e.g., "Stock" or "Market"). + - ``expected_return`` (``float``): Expected return of the asset. + - ``volatility`` (``float``): Volatility of the asset. + - ``skew`` (``float``): Skewness of the asset. + - ``kurtosis`` (``float``): Kurtosis of the asset. + + The ``Asset`` class provides methods to compute various quantities such as expected return, + volatility, skewness, and kurtosis based on the historical price data of the asset. + + """ + + def __init__(self, data: pd.Series, name: str, asset_type: str = 'Market index') -> None: + """ + :Input: + :data: ``pandas.Series``, of asset prices + :name: ``str``, Name of the asset + :asset_type: ``str`` (default: ``'Market index'``), Type of the asset (e.g., "Stock" or "Market index") + """ + self.data = data + self.name = name + # compute expected return and volatility of asset + self.expected_return = self.comp_expected_return() + self.volatility = self.comp_volatility() + self.skew = self._comp_skew() + self.kurtosis = self._comp_kurtosis() + # type of asset + self.asset_type = asset_type + + # functions to compute quantities + def comp_daily_returns(self) -> pd.Series: + """Computes the daily returns (percentage change) of the asset. + See ``finquant.returns.daily_returns``. + """ + return daily_returns(self.data) + + def comp_expected_return(self, freq=252) -> float: + """Computes the Expected Return of the asset. + See ``finquant.returns.historical_mean_return``. + + :Input: + :freq: ``int`` (default: ``252``), number of trading days, default + value corresponds to trading days in a year + + :Output: + :expected_return: ``float``, Expected Return of asset. + """ + return historical_mean_return(self.data, freq=freq) + + def comp_volatility(self, freq=252) -> float: + """Computes the Volatility of the asset. + + :Input: + :freq: ``int`` (default: ``252``), number of trading days, default + value corresponds to trading days in a year + + :Output: + :volatility: ``float``, Volatility of asset. + """ + return self.comp_daily_returns().std() * np.sqrt(freq) + + def _comp_skew(self) -> float: + """Computes and returns the skewness of the asset.""" + return self.data.skew() + + def _comp_kurtosis(self) -> float: + """Computes and returns the kurtosis of the asset.""" + return self.data.kurt() + + def properties(self): + """Nicely prints out the properties of the asset, + with customized messages based on the asset type. + """ + # nicely printing out information and quantities of the asset + string = "-" * 50 + string += f"\n{self.asset_type}: {self.name}" + string += f"\nExpected Return: {self.expected_return:0.3f}" + string += f"\nVolatility: {self.volatility:0.3f}" + string += f"\nSkewness: {self.skew:0.5f}" + string += f"\nKurtosis: {self.kurtosis:0.5f}" + string += "\n" + "-" * 50 + print(string) + + def __str__(self): + # print short description + string = f"Contains information about {self.asset_type}: {self.name}." + return string \ No newline at end of file diff --git a/finquant/market.py b/finquant/market.py index d86869ec..b4652591 100644 --- a/finquant/market.py +++ b/finquant/market.py @@ -1,15 +1,33 @@ -"""This module provides a public class ``Market`` that holds and calculates quantities of a market index""" +""" +This module provides a public class ``Market`` that represents a market index. +It serves as a specialized asset class within the finquant library. + +A market index represents the performance of a specific market or a segment of the market, +such as the S&P 500 or NASDAQ. The ``Market`` class is designed to hold and calculate quantities +related to a market index, such as expected return, volatility, skewness, and kurtosis. + +The ``Market`` class extends the ``Asset`` class from ``finquant.asset`` and inherits its +common functionality and attributes for financial assets. It provides additional methods +and attributes specific to market indices. + +""" import numpy as np import pandas as pd - +from finquant.asset import Asset from finquant.returns import daily_returns, historical_mean_return -class Market: - """Object that contains information about a market index. - To initialise the object, it requires a name and information about - the index given as ``pandas.Series`` data structure. +class Market(Asset): + """ + Class representing a market index. + + :param data: Historical price data of the market index as a ``pandas.Series``. + + The ``Market`` class extends the ``Asset`` class and represents a specific type of asset, + specifically a market index. + It requires historical price data for the market index to initialize an instance. + """ def __init__(self, data: pd.Series) -> None: @@ -17,70 +35,12 @@ def __init__(self, data: pd.Series) -> None: :Input: :data: ``pandas.Series`` of market index prices """ - self.name = data.name - self.data = data - # compute expected return and volatility of market index - self.expected_return = self.comp_expected_return() - self.volatility = self.comp_volatility() - self.skew = self._comp_skew() - self.kurtosis = self._comp_kurtosis() + super().__init__(data, name=data.name, asset_type="Market index") self.daily_returns = self.comp_daily_returns() # functions to compute quantities def comp_daily_returns(self) -> pd.Series: """Computes the daily returns (percentage change) of the market index. - See ``finance_portfolio.returns.daily_returns``. + See ``finquant.returns.daily_returns``. """ - return daily_returns(self.data) - - def comp_expected_return(self, freq=252) -> float: - """Computes the Expected Return of the market index. - See ``finance_portfolio.returns.historical_mean_return``. - - :Input: - :freq: ``int`` (default: ``252``), number of trading days, default - value corresponds to trading days in a year - - :Output: - :expected_return: Expected Return of market index. - """ - return historical_mean_return(self.data, freq=freq) - - def comp_volatility(self, freq=252) -> float: - """Computes the Volatility of the market index. - - :Input: - :freq: ``int`` (default: ``252``), number of trading days, default - value corresponds to trading days in a year - - :Output: - :volatility: volatility of market index. - """ - return self.comp_daily_returns().std() * np.sqrt(freq) - - def _comp_skew(self) -> float: - """Computes and returns the skewness of the market index.""" - return self.data.skew() - - def _comp_kurtosis(self) -> float: - """Computes and returns the Kurtosis of the market index.""" - return self.data.kurt() - - def properties(self): - """Nicely prints out the properties of the market index: - Expected Return, Volatility, Skewness, and Kurtosis. - """ - # nicely printing out information and quantities of market index - string = "-" * 50 - string += f"\nMarket index: {self.name}" - string += f"\nExpected Return:{self.expected_return:0.3f}" - string += f"\nVolatility: {self.volatility:0.3f}" - string += f"\nSkewness: {self.skew:0.5f}" - string += f"\nKurtosis: {self.kurtosis:0.5f}" - string += "-" * 50 - print(string) - - def __str__(self): - # print short description - string = "Contains information about market index " + str(self.name) + "." - return string + return daily_returns(self.data) \ No newline at end of file diff --git a/finquant/stock.py b/finquant/stock.py index 924f545d..b67b75b2 100644 --- a/finquant/stock.py +++ b/finquant/stock.py @@ -1,96 +1,63 @@ -""" This module provides a public class ``Stock`` that holds and calculates quantities of a single stock. -Instances of this class are used in the ``Portfolio`` class (provided in ``finquant.portfolio``). -Every time a new instance of ``Stock`` is added to ``Portfolio``, the quantities of the portfolio are updated. +""" +This module provides a public class ``Stock`` that represents a single stock or fund. +Instances of this class are used within the ``Portfolio`` class (provided in ``finquant.portfolio``). + +The ``Stock`` class is designed to hold and calculate quantities related to a single stock or fund. +To initialize an instance of ``Stock``, it requires the following information: + + - ``investmentinfo``: Information about the stock or fund provided as a ``pandas.DataFrame``. + The required column labels are ``Name`` and ``Allocation`` for the stock/fund name and allocation, + respectively. However, the DataFrame can contain more information beyond these columns, + such as year, strategy, currency (CCY), etc. + + - ``data``: Historical price data for the stock or fund provided as a ``pandas.DataFrame``. + The data must contain ` - Adj. Close`, which represents the closing price used to + compute the return on investment. The DataFrame can contain additional columns as well. + +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). + +The ``Stock`` class inherits from the ``Asset`` class in ``finquant.asset``, which provides common +functionality and attributes for financial assets. + """ import numpy as np import pandas as pd - +from finquant.asset import Asset from finquant.returns import daily_returns, historical_mean_return -class Stock: - """Object that contains information about a stock/fund. - To initialise the object, it requires a name, information about - the stock/fund given as one of the following data structures: +class Stock(Asset): + """Class that contains information about a stock/fund. - - ``pandas.Series`` - - ``pandas.DataFrame`` + :param investmentinfo: Investment information for the stock as a ``pandas.DataFrame``. + :param data: Historical price data for the stock as a ``pandas.DataFrame``. - The investment information can contain as little information as its name, - and the amount invested in it, the column labels must be ``Name`` and ``Allocation`` - respectively, but it can also contain more information, such as + The ``Stock`` class extends the ``Asset`` class and represents a specific type of asset, + namely a stock within a portfolio. + It requires investment information and historical price data for the stock to initialize an instance. - - Year - - Strategy - - CCY - - etc. + 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. - It also requires either data, e.g. daily closing prices as a - ``pandas.DataFrame`` or ``pandas.Series``. - ``data`` must be given as a ``pandas.DataFrame``, and at least one data column - is required to containing the closing price, hence it is required to - contain one column label `` - Adj. Close`` which is used to - compute the return of investment. However, ``data`` can contain more - data in additional columns. """ - def __init__(self, investmentinfo: pd.DataFrame, data: pd.Series): + + def __init__(self, investmentinfo: pd.DataFrame, data: pd.Series) -> None: """ :Input: :investmentinfo: ``pandas.DataFrame`` of investment information - :data: ``pandas.DataFrame`` of stock price + :data: ``pandas.Series`` of stock price """ self.name = investmentinfo.Name self.investmentinfo = investmentinfo - self.data = data - # compute expected return and volatility of stock - self.expected_return = self.comp_expected_return() - self.volatility = self.comp_volatility() - self.skew = self._comp_skew() - self.kurtosis = self._comp_kurtosis() + super().__init__(data, self.name, asset_type="Stock") # beta parameter of stock (CAPM) self.beta = None - # functions to compute quantities - def comp_daily_returns(self): - """Computes the daily returns (percentage change). - See ``finquant.returns.daily_returns``. - """ - return daily_returns(self.data) - - def comp_expected_return(self, freq=252): - """Computes the Expected Return of the stock. - - :Input: - :freq: ``int`` (default: ``252``), number of trading days, default - value corresponds to trading days in a year - - :Output: - :expected_return: Expected Return of stock. - """ - return historical_mean_return(self.data, freq=freq) - - def comp_volatility(self, freq=252): - """Computes the Volatility of the stock. - - :Input: - :freq: ``int`` (default: ``252``), number of trading days, default - value corresponds to trading days in a year - - :Output: - :volatility: Volatility of stock. - """ - return self.comp_daily_returns().std() * np.sqrt(freq) - - def _comp_skew(self): - """Computes and returns the skewness of the stock.""" - return self.data.skew() - - def _comp_kurtosis(self): - """Computes and returns the Kurtosis of the stock.""" - return self.data.kurt() - def comp_beta(self, market_daily_returns: pd.Series) -> float: """Compute and return the Beta parameter of the stock. @@ -98,7 +65,7 @@ def comp_beta(self, market_daily_returns: pd.Series) -> float: :market_daily_returns: ``pd.Series``, daily returns of the market :Output: - :sharpe: ``float``, the Beta parameter of the stock + :beta: ``float``, the Beta parameter of the stock """ cov_mat = np.cov( self.comp_daily_returns(), @@ -116,18 +83,14 @@ def properties(self): """ # nicely printing out information and quantities of the stock string = "-" * 50 - string += f"\nStock: {self.name}" + string += f"\n{self.asset_type}: {self.name}" string += f"\nExpected Return: {self.expected_return:0.3f}" string += f"\nVolatility: {self.volatility:0.3f}" string += f"\nSkewness: {self.skew:0.5f}" string += f"\nKurtosis: {self.kurtosis:0.5f}" + if self.beta is not None: + string += f"\n{self.asset_type} Beta: {self.beta:0.3f}" string += "\nInformation:" string += "\n" + str(self.investmentinfo.to_frame().transpose()) - string += "\n" - string += "-" * 50 - print(string) - - def __str__(self): - # print short description - string = "Contains information about " + str(self.name) + "." - return string + string += "\n" + "-" * 50 + print(string) \ No newline at end of file diff --git a/version b/version index 20ad742c..f8832652 100644 --- a/version +++ b/version @@ -1,2 +1,2 @@ -version=0.3.1 -release=0.3.1 +version=0.3.2 +release=0.3.2