Open in Colab

You can run the code examples in Google Colab.

To install the package:

[ ]:
%%capture --no-stderr
%pip install --quiet -U okama

Import okama and matplotlib packages.

[2]:
import warnings

import matplotlib.pyplot as plt

import okama as ok

warnings.filterwarnings("ignore")

plt.rcParams["figure.figsize"] = [12.0, 6.0]

AssetList has a set of useful methods for tracking the performance of index funds and comparing them with benchmarks:

  • tracking difference

  • tracking error

  • beta

  • rolling and cumulative correlations

Tracking difference

Tracking difference is calculated as the difference between the accumulated return of an index and that of the ETFs that replicate it.

Let’s compare the main S&P 500 ETFs.

[3]:
symbols = [
    "SP500TR.INDX",
    "SPY.US",
    "VOO.US",
    "IVV.US",
]  # the benchmark symbol should be in the first place
sp = ok.AssetList(symbols, last_date="2022-04")
sp
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
File ~\PycharmProjects\okama\okama\api\api_methods.py:52, in API.connect(cls, endpoint, symbol, first_date, last_date, period)
     51     r = session.get(request_url, params=params, verify=True, timeout=cls.default_timeout)
---> 52     r.raise_for_status()
     53 except requests.exceptions.HTTPError as errh:

File c:\Users\Komov\miniconda3\envs\py13\Lib\site-packages\requests\models.py:1026, in Response.raise_for_status(self)
   1025 if http_error_msg:
-> 1026     raise HTTPError(http_error_msg, response=self)

HTTPError: 400 Client Error: BAD REQUEST for url: https://api.okama.io/api/ts/macro/USD.INFL?first_date=2010-10-01+00%3A00%3A00&last_date=2022-04-01+00%3A00%3A00&period=M

The above exception was the direct cause of the following exception:

HTTPError                                 Traceback (most recent call last)
Cell In[3], line 7
      1 symbols = [
      2     "SP500TR.INDX",
      3     "SPY.US",
      4     "VOO.US",
      5     "IVV.US",
      6 ]  # the benchmark symbol should be in the first place
----> 7 sp = ok.AssetList(symbols, last_date="2022-04")
      8 sp

File ~\PycharmProjects\okama\okama\common\make_asset_list.py:78, in ListMaker.__init__(self, assets, first_date, last_date, ccy, inflation)
     76 if inflation:
     77     self.inflation: str = f"{ccy}.INFL"
---> 78     self._inflation_instance = macro.Inflation(self.inflation, self.first_date, self.last_date)
     79     self.inflation_first_date: pd.Timestamp = self._inflation_instance.first_date
     80     self.inflation_last_date: pd.Timestamp = self._inflation_instance.last_date

File ~\PycharmProjects\okama\okama\macro.py:242, in Inflation.__init__(self, symbol, first_date, last_date)
    236 def __init__(
    237     self,
    238     symbol: str = settings.default_macro_inflation,
    239     first_date: Union[str, pd.Timestamp, None] = None,
    240     last_date: Union[str, pd.Timestamp, None] = None,
    241 ):
--> 242     super().__init__(
    243         symbol,
    244         first_date=first_date,
    245         last_date=last_date,
    246     )

File ~\PycharmProjects\okama\okama\macro.py:41, in MacroABC.__init__(self, symbol, first_date, last_date)
     39 self._first_date = first_date
     40 self._last_date = last_date
---> 41 self._values_monthly = self._get_values_monthly()
     42 self._set_first_last_dates()
     44 self._pl_txt = f"{self.pl.years} years, {self.pl.months} months"

File ~\PycharmProjects\okama\okama\macro.py:71, in MacroABC._get_values_monthly(self)
     70 def _get_values_monthly(self) -> pd.Series:
---> 71     return data_queries.QueryData.get_macro_ts(self.symbol, self._first_date, self._last_date, period="M")

File ~\PycharmProjects\okama\okama\api\data_queries.py:51, in QueryData.get_macro_ts(symbol, first_date, last_date, period)
     39 @staticmethod
     40 def get_macro_ts(
     41     symbol: str,
   (...)     44     period: str = "M",
     45 ) -> pd.Series:
     46     """
     47     Requests api_methods.API for Macroeconomic indicators time series (monthly data).
     48     - Inflation time series
     49     - Bank rates time series
     50     """
---> 51     csv_input = api_methods.API.get_macro(symbol=symbol, first_date=first_date, last_date=last_date, period=period)
     52     return QueryData.csv_to_series(csv_input, period=period)

File ~\PycharmProjects\okama\okama\api\api_methods.py:160, in API.get_macro(cls, symbol, first_date, last_date, period)
    149 @classmethod
    150 def get_macro(
    151     cls,
   (...)    155     period: str = "m",
    156 ):
    157     """
    158     Get macro time series (monthly).
    159     """
--> 160     return cls.connect(
    161         endpoint=cls.endpoint_macro,
    162         symbol=symbol,
    163         first_date=first_date,
    164         last_date=last_date,
    165         period=period,
    166     )

File ~\PycharmProjects\okama\okama\api\api_methods.py:57, in API.connect(cls, endpoint, symbol, first_date, last_date, period)
     55         raise requests.exceptions.HTTPError(f"{symbol} is not found in the database.", 404) from errh
     56     if r.status_code == 400:
---> 57         raise requests.exceptions.HTTPError(
     58             f"Bad request for {symbol}: {r.text}",
     59             r.status_code,
     60             request_url,
     61         ) from errh
     62     raise requests.exceptions.HTTPError(
     63         f"HTTP error fetching data for {symbol}:",
     64         r.status_code,
   (...)     67         request_url,
     68     ) from errh
     69 return r.text

HTTPError: [Errno Bad request for USD.INFL: {"message":"first_date: Value error, The date should be in YYYY-MM-DD, YYYY-MM, DD-MM-YYYY, or MM-YYYY format; last_date: Value error, The date should be in YYYY-MM-DD, YYYY-MM, DD-MM-YYYY, or MM-YYYY format"}
] 400: 'https://api.okama.io/api/ts/macro/USD.INFL'
[ ]:
sp.names
{'SP500TR.INDX': 'S&P 500 TR (Total Return)',
 'SPY.US': 'SPDR S&P 500 ETF Trust',
 'VOO.US': 'Vanguard S&P 500 ETF',
 'IVV.US': 'iShares Core S&P 500 ETF'}

We can check the overall tracking difference as the difference in accumulated returns.

[ ]:
sp.tracking_difference().plot();
../_images/jupyter_funds_13_0.png

… or see annualized values.

[ ]:
sp.tracking_difference_annualized().plot();
../_images/jupyter_funds_15_0.png
However, the tracking difference above includes 11 years of historical data. Sometimes it is more useful to focus on recent performance.
Use rolling tracking difference for that purpose:
[ ]:
sp.tracking_difference_annualized(rolling_window=5 * 12).plot();  # rolling window is 5 years
../_images/jupyter_funds_17_0.png

The rolling_window parameter is also available in tracking_error(), index_beta(), and index_corr().

Tracking Error

Tracking Error is the standard deviation of the difference between the ETF and index returns.

[ ]:
sp.tracking_error().plot();
../_images/jupyter_funds_21_0.png

… or rolling tracking error:

[ ]:
sp.tracking_error(rolling_window=12 * 2).plot();
../_images/jupyter_funds_23_0.png

Beta

Another popular measure for index funds is beta.
Beta (β) measures the volatility of an asset’s returns relative to the overall market.
[ ]:
sp.index_beta().plot();  # in this example as expected beta is close to 1. Each ETF represents broad market in form of S&P 500 index
../_images/jupyter_funds_26_0.png

We can see that all three funds have similar beta values.

Rolling beta is also available:

[ ]:
sp.index_beta(rolling_window=12).plot();
../_images/jupyter_funds_29_0.png

Correlation with index

Sometimes it is useful to check the correlation between different asset types.

In this example we compare the correlation between:

  • US stocks (S&P 500 index)

  • US bonds (Vanguard mutual fund)

  • gold (spot prices)

  • US real estate (REIT ETF)

[ ]:
assets = ["SP500TR.INDX", "VBMFX.US", "GC.COMM", "VNQ.US"]  # GC.COMM - gold spot prices
x = ok.AssetList(assets)
x
assets           [SP500TR.INDX, VBMFX.US, GC.COMM, VNQ.US]
currency                                               USD
first_date                                         2004-10
last_date                                          2025-05
period_length                           20 years, 8 months
inflation                                         USD.INFL
dtype: object
[ ]:
x.names
{'SP500TR.INDX': 'S&P 500 TR (Total Return)',
 'VBMFX.US': 'VANGUARD TOTAL BOND MARKET INDEX FUND INVESTOR SHARES',
 'GC.COMM': 'Gold (COMEX)',
 'VNQ.US': 'Vanguard Real Estate Index Fund ETF Shares'}
It is possible to see how the correlation changed over time.
.index_corr() calculates expanding or rolling correlation with the benchmark.
[ ]:
x.index_corr().plot();  # expanding correlation rolling_window = None
../_images/jupyter_funds_36_0.png
Sometimes it is better to look at the rolling correlation with the benchmark (S&P 500 Total Return index).
The rolling period is set with the rolling_window parameter in months.
[ ]:
x.index_corr(rolling_window=12 * 5).plot();  # rolling window size is 5 years
../_images/jupyter_funds_38_0.png

It can be clearly seen that the real estate ETF (VNQ) had a higher correlation with stocks than gold or bonds during this period.

[ ]: