Project 1: Momentum Strategies

By: Chengyi (Jeff) Chen

Description of The Project:

We want to test the momentum effect on different assets. We have daily price time series information for assets in the following asset classes:

  1. Equity Index

  2. Credit (Corporate Bonds)

  3. Govt Bond (Sovereign Bonds)

  4. Currencies

  5. Commodities

  6. Equity Factors

You choose one of the above 6 asset classes, and we’ll build momentum portfolios for each one of the assets in the chosen asset class.

For every asset, based on its price time series, a momentum portfolio could be built in the following way: Step 1: starting from 12/31/2001, at each month-end time point, calculate the asset’s return in the past 12 months, if the return is positive (negative), the portfolio longs (shorts) 100% of the asset and holds the long (short) position for the next month. Step 2: calculate the monthly returns for the above-built portfolio till 2020/8/31. Step 3: prepare the deliverable table as follows:

../../_images/project_1_deliverable.png

Bonus problem At step 1, at each month-end date, calculate all asset returns in the past 12 months,
and allocate 50% weight to the top two performing assets respectively; and -50% to the bottom two performing assets respectively in your portfolio. And then do step 2 and step 3.

%load_ext nb_black
%load_ext autotime

import pandas as pd
import numpy as np
from typing import Dict
from datetime import datetime, timedelta
from abc import ABC, abstractmethod


def get_asset_class_data(raw_data: pd.DataFrame) -> pd.DataFrame:
    """Splits the Raw dataframe into the separate asset 
    class dataframes and return them in a MultiIndex Dataframe
    """
    asset_class_df_list = np.split(
        raw_data, np.argwhere(raw_data.isnull().all(axis=0).values).flatten(), axis=1
    )
    asset_class_dfs = {}
    for df in asset_class_df_list:
        df = df.dropna(how="all", axis=1)
        asset_class_name = df.iloc[0, 0]
        columns = df.iloc[0, :].str.cat(df.iloc[1, :], sep=" - ")
        columns.iloc[0] = "Date"
        df.columns = columns
        df = df.drop([0, 1], axis=0)
        df = df.set_index("Date")
        asset_class_dfs[
            asset_class_name
        ] = df.sort_index()  # Earliest stock price on top
    return pd.concat(asset_class_dfs, names=["asset_class", "security"], axis=1)


class TradingStrategy(ABC):
    def __init__(
        self, name: str, asset_class_history: pd.DataFrame, asset_class="Equity Idx"
    ):
        self.name = name
        self.asset_class = asset_class
        self.asset_class_history = asset_class_history

    @abstractmethod
    def execute_backtest(self):
        """"""
        pass

    @abstractmethod
    def _results(self):
        """"""
        pass

    @staticmethod
    def annualized_volatility(returns):
        returns = returns.dropna(axis=0)
        return np.std(returns) * np.sqrt(len(returns)) if len(returns) > 0 else None

    @staticmethod
    def annualized_return(returns):
        returns = returns.dropna(axis=0)
        return (
            (
                np.prod(np.ones(len(returns)) + np.array(returns)) ** (1 / len(returns))
                - 1
            )
            if len(returns) > 0
            else None
        )

    @staticmethod
    def calendar_year_return(returns):
        returns = returns.dropna(axis=0)
        return (
            ((TradingStrategy.annualized_return(returns) + 1) ** 12) - 1
            if len(returns) > 0
            else None
        )

    @staticmethod
    def sharpe_ratio(returns, R_f=0):
        returns = returns.dropna(axis=0)
        R_p = TradingStrategy.annualized_return(returns)
        σ_p = TradingStrategy.annualized_volatility(returns)
        return (R_p - R_f) / σ_p if len(returns) > 0 else None
data = get_asset_class_data(
    pd.read_excel(
        "./data/Project1_TimeSeries_Momentum_Due20200913.xlsx",
        sheet_name=2,
        header=None,
    )
)
time: 3.34 s

Time Series Momentum Strategy

class TimeSeriesMomentumStrategy(TradingStrategy):
    """Longs (Shorts) 100% for 1 month if asset's own past 12 month returns > 0 (< 0) 
    Assumes that all transactions happen on the end of month
    """

    def __init__(self, momentum_signal_period: int = 12, **kwargs):
        super().__init__(name="Time Series Momentum Strategy", **kwargs)
        self.momentum_signal_period = momentum_signal_period
        self.target_asset_history = self.asset_class_history[
            self.asset_class_history.columns[
                self.asset_class_history.columns.get_level_values("asset_class")
                == asset_class
            ]
        ]

    def filter_month_end(self, df):
        """Filter out only data of end of month"""
        dates = df.index
        only_month_end_df = df[pd.Series(dates, index=dates).dt.is_month_end]
        return only_month_end_df

    def check_data(self):
        """"""
        # Check if there's enough data from the past data for a momentum strategy
        try:
            self.start_date_idx = np.argwhere(
                self.only_month_end_df.index
                == datetime.strptime(self.start_date, self.date_format)
            ).flatten()[0]

            self.end_date_idx = np.argwhere(
                self.only_month_end_df.index
                == datetime.strptime(self.end_date, self.date_format)
            ).flatten()[0]
        except:
            raise ValueError(f"{start_date} not available in asset history data.")
        assert self.start_date_idx - 1 >= self.momentum_signal_period, print(
            f"There is not enough data (Number of months < {self.momentum_signal_period}) before {self.start_date} to generate a momentum signal."
        )

    def execute_backtest(
        self,
        start_date: str = "2001-12-31",
        end_date: str = "2020-08-31",
        date_format: str = "%Y-%m-%d",
    ):
        """Contains the actual trading logic """
        self.start_date = start_date
        self.end_date = end_date
        self.date_format = date_format
        self.only_month_end_df = self.filter_month_end(df=self.target_asset_history)
        self.check_data()
        self.past_12_month_returns = self.only_month_end_df.pct_change(
            self.momentum_signal_period
        ).iloc[
            self.start_date_idx : self.end_date_idx + 1
        ]  # Cumulative returns from past 12 months
        self.monthly_long_position_returns = (
            self.only_month_end_df.iloc[self.start_date_idx - 1 :]
            .pct_change(1)
            .iloc[1 : self.end_date_idx + 1]
        )  # End of Month Monthly Long position returns
        self.monthly_returns = (
            self.past_12_month_returns.apply(np.sign).shift(1).iloc[1:]
            * self.monthly_long_position_returns.iloc[1:]
        )
        self._results()

    def _results(self):
        """"""
        self.annual_results = self.monthly_returns.groupby(
            [self.monthly_returns.index.year]
        ).agg([self.calendar_year_return])
        self.asset_class_annualized_returns = self.annual_results.apply(
            self.annualized_return, axis=0
        ).droplevel(-1)
        self.asset_class_annualized_returns.name = "Annualized Returns"
        self.asset_class_annualized_volatility = (
            self.monthly_returns.groupby([self.monthly_returns.index.year])
            .agg([self.annualized_volatility])
            .apply(self.annualized_volatility, axis=0)
        ).droplevel(-1)
        self.asset_class_annualized_volatility.name = "Annualized Volatility"
        self.asset_class_sharpe_ratio = self.annual_results.apply(
            self.sharpe_ratio, axis=0
        ).droplevel(-1)
        self.asset_class_sharpe_ratio.name = "Sharpe Ratio (assuming $R_f$ = 0)"
        display(self.annual_results)
        display(
            pd.concat(
                [
                    self.asset_class_annualized_returns,
                    self.asset_class_annualized_volatility,
                    self.asset_class_sharpe_ratio,
                ],
                axis=1,
            ).transpose()
        )
time: 58.3 ms
for asset_class in data.columns.get_level_values("asset_class").unique():
    print("=" * 100)
    tsms = TimeSeriesMomentumStrategy(asset_class=asset_class, asset_class_history=data)
    tsms.execute_backtest()
====================================================================================================
Equity Idx
S&P 500 - SPX Russell 2K - RTY NASDAQ 100 - NDX Hang Seng - HSI Shanghai SE - SHCOMP KOSPI - KOSPI ASX 200 - AS51 NIKKEI - NKY FTSE 100 - UKX DAX - DAX FTSE MIB - FTSEMIB CAC - CAC MSCI India - M1IN MSCI Russia - M1RU MSCI Brazil - M1BR
calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return
Date
2002 0.088434 0.279880 0.387414 0.142797 0.135255 -0.071996 0.051451 0.169137 0.232031 0.535081 0.264682 0.353556 0.222429 0.154326 NaN
2003 0.019208 -0.086041 -0.014318 0.113109 -0.199595 -0.015477 0.009032 -0.166724 -0.008626 0.003490 0.020381 -0.058143 0.919416 0.140371 NaN
2004 0.108757 0.182753 0.106847 0.171412 -0.074865 0.137561 0.295137 0.086359 0.112444 0.073369 0.188540 0.101350 0.191084 0.054620 NaN
2005 0.049069 0.045117 -0.123204 0.084047 0.018237 0.567997 0.240955 0.278645 0.208053 0.270714 0.201198 0.269533 0.375694 0.700018 NaN
2006 0.157767 0.183298 -0.024914 0.389660 0.988308 0.060301 0.254948 0.080900 0.144439 0.219786 0.209513 0.209697 0.510044 0.555970 NaN
2007 0.055713 -0.014266 0.191648 0.433789 0.979663 0.237113 0.174193 -0.058043 0.073884 0.222892 -0.031965 0.041919 0.731106 0.245037 NaN
2008 0.330330 0.385477 0.202456 0.055844 0.227160 0.213187 0.136177 0.516562 0.108632 0.048457 0.730014 0.198327 -0.159019 0.501275 NaN
2009 -0.119551 -0.097311 -0.273061 -0.360089 -0.359757 -0.290330 -0.251316 -0.120017 -0.145776 -0.091792 -0.184650 -0.107100 -0.545699 -0.578876 NaN
2010 0.150587 0.268146 0.200588 0.085812 -0.441488 0.235763 0.029409 -0.148346 0.126689 0.160599 -0.327295 -0.115950 0.209516 0.190655 NaN
2011 0.021055 -0.293698 0.036123 -0.271779 -0.011050 -0.228118 -0.140106 -0.104074 -0.169795 -0.193300 -0.152196 -0.111474 -0.061607 -0.317564 -0.534078
2012 0.068127 0.051368 0.183478 -0.103584 -0.094842 -0.012624 0.026742 -0.029484 0.066475 -0.097605 0.004187 -0.053662 -0.332252 -0.249583 -0.079200
2013 0.323742 0.388219 0.369096 0.065616 0.069251 0.019623 0.219773 0.593699 0.186501 0.254817 -0.006969 0.222258 -0.396076 -0.092174 -0.058150
2014 0.136783 0.048962 0.193963 -0.021945 0.624548 -0.016952 0.071487 0.089566 0.007179 0.026527 0.029781 0.025418 0.048017 0.137143 -0.070510
2015 -0.143971 -0.044132 0.097507 -0.086906 0.111542 0.002824 0.100783 0.109935 -0.024950 0.095605 0.157532 0.119448 -0.055465 -0.141711 0.516565
2016 -0.020077 -0.020325 -0.143435 -0.130668 -0.303595 -0.057955 -0.028356 -0.324395 0.043638 -0.225592 -0.217988 -0.150836 -0.182770 0.186032 -0.466803
2017 0.218228 0.146277 0.329921 0.412730 0.049283 0.239290 0.134067 0.213360 0.119541 0.125126 0.244154 0.125445 0.271831 0.052043 0.241117
2018 -0.043918 -0.110274 0.000368 -0.170337 -0.044080 0.060109 -0.014635 -0.103034 0.009453 -0.031980 0.036292 0.054855 -0.245235 -0.003945 -0.524253
2019 0.050027 -0.222140 0.394574 -0.177570 -0.254777 0.020049 0.156804 -0.174382 -0.038051 -0.139648 -0.089326 -0.078987 -0.074335 0.128606 -0.065489
2020 -0.219109 -0.324053 0.648726 -0.103680 0.079134 0.181846 -0.497120 -0.374425 -0.107319 -0.505415 -0.514804 -0.486342 -0.668994 -0.635793 0.124278
Equity Idx
S&P 500 - SPX Russell 2K - RTY NASDAQ 100 - NDX Hang Seng - HSI Shanghai SE - SHCOMP KOSPI - KOSPI ASX 200 - AS51 NIKKEI - NKY FTSE 100 - UKX DAX - DAX FTSE MIB - FTSEMIB CAC - CAC MSCI India - M1IN MSCI Russia - M1RU MSCI Brazil - M1BR
Annualized Returns 0.055576 0.019841 0.124717 0.005114 0.019207 0.051567 0.030362 -0.000177 0.043581 0.012956 -0.004439 0.009452 -0.044851 -0.011820 -0.153728
Annualized Volatility 0.250217 0.258396 0.299556 0.279455 0.396346 0.309805 0.180394 0.215803 0.201919 0.342666 0.298922 0.263166 0.399527 0.422950 0.262285
Sharpe Ratio (assuming $R_f$ = 0) 0.091799 0.022376 0.132030 0.005419 0.011392 0.063658 0.037631 -0.000164 0.089658 0.013405 -0.003924 0.011559 -0.025470 -0.008077 -0.149786
====================================================================================================
Credit
US IG - LUACTRUU US HY - H0A0 EU IG - SPEZICUT EU HY - HE00 EM Corp - EMCB
calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return
Date
2002 0.101171 -0.114729 NaN 0.033558 0.107632
2003 0.082424 0.206790 NaN 0.130784 0.171954
2004 0.044620 0.108698 NaN 0.145615 0.100654
2005 0.016762 0.027381 NaN 0.059634 0.043976
2006 0.013826 0.117671 NaN 0.111028 0.076199
2007 0.045608 0.021929 NaN -0.022205 0.049376
2008 -0.129893 0.258021 NaN 0.436900 0.013024
2009 0.064239 -0.188552 NaN -0.256437 -0.155023
2010 0.089958 0.151904 NaN 0.142566 0.114395
2011 0.081460 0.043827 NaN -0.110336 -0.056776
2012 0.098174 0.155834 NaN 0.115463 0.157965
2013 -0.036943 0.074187 NaN 0.100641 -0.005148
2014 0.036368 0.025033 NaN 0.054770 -0.016850
2015 -0.021545 0.060394 NaN 0.007552 0.051350
2016 -0.019228 -0.020808 -0.066869 -0.012205 0.092452
2017 0.064178 0.074832 0.025810 0.067444 0.081342
2018 -0.040784 -0.022649 0.000440 0.010756 -0.030833
2019 0.092737 0.043631 -0.091023 0.025470 0.069415
2020 0.105935 -0.318564 -0.265735 -0.364253 -0.034163
Credit
US IG - LUACTRUU US HY - H0A0 EU IG - SPEZICUT EU HY - HE00 EM Corp - EMCB
Annualized Returns 0.034379 0.027816 -0.085631 0.022193 0.040705
Annualized Volatility 0.110831 0.211764 0.021668 0.250086 0.209394
Sharpe Ratio (assuming $R_f$ = 0) 0.128956 0.048584 -0.373882 0.031851 0.119703
====================================================================================================
Govt Bond
US Treasury - LUATTRUU UK Gilt - JFBG3GUS German Bund - CBKIG0FT Japan JGB - SPJGBTR EM Govt - GBIEMCOR
calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return
Date
2002 0.117935 0.066458 0.105547 0.051543 NaN
2003 0.022414 0.006415 0.020399 -0.041043 NaN
2004 0.007724 0.003808 0.042187 0.005642 NaN
2005 0.027919 0.028276 0.042376 0.006645 NaN
2006 -0.001478 -0.030992 -0.051699 -0.013907 NaN
2007 0.090124 -0.005503 0.020445 0.040500 NaN
2008 0.137371 0.104842 -0.027833 0.035918 NaN
2009 -0.035674 -0.032949 -0.000288 0.025105 -0.088812
2010 0.004481 0.047323 0.027167 0.024459 0.154323
2011 0.098133 0.150250 0.052122 0.027175 -0.125680
2012 0.019934 0.051032 0.067543 0.018526 -0.179821
2013 0.007119 0.053977 -0.021530 0.004020 -0.070201
2014 -0.007403 0.050203 0.077912 0.040626 -0.063224
2015 0.008425 0.005043 0.020838 0.014520 0.155773
2016 0.010376 0.038564 0.070891 0.010483 -0.165256
2017 -0.015598 0.037761 -0.047833 -0.013668 0.146637
2018 -0.065910 -0.012898 0.004869 0.014964 -0.024602
2019 0.068576 0.047258 0.048424 -0.003778 -0.063896
2020 0.134123 0.074121 0.042626 -0.024609 -0.350187
Govt Bond
US Treasury - LUATTRUU UK Gilt - JFBG3GUS German Bund - CBKIG0FT Japan JGB - SPJGBTR EM Govt - GBIEMCOR
Annualized Returns 0.031570 0.035009 0.025165 0.011475 -0.067697
Annualized Volatility 0.055405 0.073377 0.049919 0.043905 0.084537
Sharpe Ratio (assuming $R_f$ = 0) 0.128530 0.180850 0.139315 0.113346 -0.135072
====================================================================================================
Currencies
EUR/USD - EURUSD JPY/USD - JPYUSD GBP/USD - GBPUSD AUD/USD - AUDUSD CNY/USD - CNYUSD
calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return
Date
2002 0.148162 -0.077826 0.150727 0.006712 -0.000331
2003 0.200438 0.053686 0.109193 0.339031 -0.000083
2004 0.076141 0.044709 0.074140 0.037633 -0.000166
2005 -0.102000 -0.064387 -0.046275 -0.021218 -0.018382
2006 -0.050245 -0.045898 -0.039035 -0.094061 0.033091
2007 0.105479 0.003912 0.013376 0.109829 0.069609
2008 -0.044681 0.231672 0.347671 -0.009721 0.070411
2009 -0.080276 -0.024317 -0.139300 -0.216322 -0.000819
2010 -0.223406 0.046534 -0.167473 0.139913 0.034906
2011 -0.100326 0.055019 -0.113537 -0.166821 0.047657
2012 -0.025019 0.088735 -0.037251 -0.068677 0.011026
2013 0.049704 0.204304 -0.144734 0.146359 0.029040
2014 -0.012627 0.127919 -0.008513 -0.066779 -0.028018
2015 0.100158 0.000587 0.049289 0.110821 0.042214
2016 -0.078979 -0.102517 0.177952 -0.111267 0.068763
2017 -0.004080 0.030011 -0.074729 0.028235 -0.039351
2018 -0.007993 -0.023951 0.009791 -0.007616 -0.034823
2019 0.020213 -0.047243 0.002487 -0.001233 0.009126
2020 -0.056399 -0.020593 -0.053442 0.002394 -0.026229
Currencies
EUR/USD - EURUSD JPY/USD - JPYUSD GBP/USD - GBPUSD AUD/USD - AUDUSD CNY/USD - CNYUSD
Annualized Returns -0.009381 0.021707 -0.001322 0.001016 0.013498
Annualized Volatility 0.136584 0.133411 0.074319 0.168132 0.073629
Sharpe Ratio (assuming $R_f$ = 0) -0.021918 0.056978 -0.002456 0.001898 0.089492
====================================================================================================
Commod
WTI Oil - CL1 COMB Brent Oil - CO1 Gold - XAUUSD Silver - XAG Copper - SPGSIC Agriculture - BCOMAGTR
calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return
Date
2002 -0.137678 -0.142562 0.247715 -0.073021 -0.196890 0.041818
2003 -0.218048 -0.163959 0.193650 -0.131105 0.081011 -0.015321
2004 0.131023 0.136938 0.055362 0.148569 0.389583 -0.165351
2005 0.404833 0.457736 0.179154 0.370638 0.401533 -0.238630
2006 -0.016730 0.152491 0.231528 0.463982 0.408749 0.108420
2007 -0.038936 0.007421 0.309408 -0.137633 -0.053572 0.298885
2008 -0.001325 -0.056533 -0.185666 -0.181195 0.671353 -0.012467
2009 -0.473938 -0.434962 0.305817 -0.231319 -0.515501 -0.009318
2010 0.151462 0.215835 0.295739 0.831556 0.308329 0.003032
2011 -0.243198 0.133298 0.100964 -0.099410 -0.342450 -0.105827
2012 -0.105795 0.074718 -0.073634 -0.421993 -0.183567 -0.257674
2013 0.055780 0.264209 0.321928 0.517521 0.116259 0.001788
2014 0.600033 0.705686 -0.068142 0.007230 0.138502 -0.026743
2015 0.230527 0.279488 -0.021973 0.095398 0.278538 0.138459
2016 -0.229107 -0.271858 -0.217889 -0.522722 -0.275194 0.011708
2017 0.032603 -0.034583 0.006521 0.005963 0.308171 0.051301
2018 -0.065707 -0.048602 -0.106186 0.126177 -0.091015 0.098412
2019 -0.163310 -0.072973 0.139826 0.035179 -0.050574 -0.036729
2020 -0.940855 -0.506588 0.476989 0.597779 -0.154135 -0.062749
Commod
WTI Oil - CL1 COMB Brent Oil - CO1 Gold - XAUUSD Silver - XAG Copper - SPGSIC Agriculture - BCOMAGTR
Annualized Returns -0.166098 -0.004430 0.098833 0.019317 0.019451 -0.017449
Annualized Volatility 0.794941 0.562311 0.221906 0.509008 0.387265 0.309227
Sharpe Ratio (assuming $R_f$ = 0) -0.120575 -0.003592 0.120173 0.013014 0.014807 -0.031794
====================================================================================================
EQ Factors
Value - DJTMNSV Size - DJTMNSS Quality - DJTMNQU Momentum - DJTMNMO Low Beta - DJTMNAB
calendar_year_return calendar_year_return calendar_year_return calendar_year_return calendar_year_return
Date
2002 NaN NaN NaN NaN NaN
2003 0.128332 -0.045934 -0.027136 -0.114272 -0.095846
2004 0.124360 0.089198 -0.016135 -0.021923 -0.020721
2005 0.015967 0.022662 0.042710 0.151704 -0.007309
2006 -0.067420 0.045008 0.006538 -0.000067 -0.155052
2007 0.064438 0.014825 0.073109 0.202739 -0.000901
2008 -0.084118 0.002846 0.159003 0.080683 0.351417
2009 0.339859 0.151237 -0.060329 -0.092819 -0.164237
2010 -0.066073 0.119517 0.051802 -0.000386 0.153621
2011 0.032565 0.015253 0.089126 0.067082 -0.299770
2012 0.083043 0.032431 0.037111 0.076830 -0.060622
2013 0.158641 0.063753 -0.003690 0.003273 0.030478
2014 0.024173 -0.068263 -0.110608 -0.003507 -0.013651
2015 -0.022226 0.051858 0.103951 0.013864 0.083934
2016 0.077949 -0.059151 -0.121989 -0.008552 -0.018173
2017 -0.087912 -0.076334 0.100932 -0.038273 0.007228
2018 0.140675 0.048635 0.044165 0.057604 0.050381
2019 -0.012805 -0.017005 0.004608 -0.199652 0.030855
2020 0.712308 0.276804 -0.165403 0.132956 0.142463
EQ Factors
Value - DJTMNSV Size - DJTMNSS Quality - DJTMNQU Momentum - DJTMNMO Low Beta - DJTMNAB
Annualized Returns 0.073872 0.033848 0.008022 0.012581 -0.008529
Annualized Volatility 0.146757 0.111505 0.153330 0.291844 0.232533
Sharpe Ratio (assuming $R_f$ = 0) 0.094764 0.095148 0.022765 0.031399 -0.014766
time: 1.35 s

Cross-sectional Momentum Strategy

class CrossSectionalMomentumStrategy(TimeSeriesMomentumStrategy):
    """Longs 50% of best performing asset in past 12 months in asset class 
    and Shorts 50% of worst performing asset in past 12 months in asset class for 1 month  
    Assumes that all transactions happen on the end of month
    """

    def __init__(self, momentum_signal_period: int = 12, **kwargs):
        TradingStrategy.__init__(
            self, name="Cross Sectional Momentum Strategy", **kwargs
        )
        self.momentum_signal_period = momentum_signal_period
        self.target_asset_history = self.asset_class_history[
            self.asset_class_history.columns[
                self.asset_class_history.columns.get_level_values("asset_class")
                == asset_class
            ]
        ]

    def execute_backtest(
        self,
        start_date: str = "2001-12-31",
        end_date: str = "2020-08-31",
        date_format: str = "%Y-%m-%d",
    ):
        """Contains the actual trading logic """
        self.start_date = start_date
        self.end_date = end_date
        self.date_format = date_format
        self.only_month_end_df = self.filter_month_end(df=self.target_asset_history)
        self.check_data()
        self.past_12_month_returns = self.only_month_end_df.pct_change(
            self.momentum_signal_period
        ).iloc[
            self.start_date_idx : self.end_date_idx + 1
        ]  # Cumulative returns from past 12 months
        self.monthly_long_position_returns = (
            self.only_month_end_df.iloc[self.start_date_idx - 1 :]
            .pct_change(1)
            .iloc[1 : self.end_date_idx + 1]
        )  # End of Month Monthly Long position returns

        long_portfolio_returns = (
            self.past_12_month_returns.apply(lambda row: (row == np.max(row)), axis=1)
            .shift(1)
            .iloc[1:]
            * self.monthly_long_position_returns.iloc[1:]
        ).sum(
            axis=1
        )  # Longs 50% of best performing asset in past 12 months in asset class

        short_portfolio_returns = -(
            self.past_12_month_returns.apply(lambda row: (row == np.min(row)), axis=1)
            .shift(1)
            .iloc[1:]
            * self.monthly_long_position_returns.iloc[1:]
        ).sum(
            axis=1
        )  # Shorts 50% of worst performing asset in past 12 months in asset class

        self.monthly_returns = (
            long_portfolio_returns * 0.5 + short_portfolio_returns * 0.5
        )  # Weight portfolios
        self._results()

    def _results(self):
        """"""
        self.annual_results = self.monthly_returns.groupby(
            [self.monthly_returns.index.year]
        ).agg([self.calendar_year_return])
        self.asset_class_annualized_returns = pd.Series(
            self.annual_results.apply(self.annualized_return, axis=0).values,
            name="Annualized Returns",
        )
        self.asset_class_annualized_volatility = pd.Series(
            (
                self.monthly_returns.groupby([self.monthly_returns.index.year])
                .agg([self.annualized_volatility])
                .apply(self.annualized_volatility, axis=0)
            ).values,
            name="Annualized Volatility",
        )
        self.asset_class_sharpe_ratio = pd.Series(
            self.annual_results.apply(self.sharpe_ratio, axis=0).values,
            name="Sharpe Ratio (assuming $R_f$ = 0)",
        )
        self.annual_results = self.annual_results.style.set_caption(self.asset_class)
        display(self.annual_results)
        summary = pd.concat(
            [
                self.asset_class_annualized_returns,
                self.asset_class_annualized_volatility,
                self.asset_class_sharpe_ratio,
            ],
            axis=1,
        ).transpose()
        summary.columns = ["Summary"]
        display(summary)
time: 63 ms
for asset_class in data.columns.get_level_values("asset_class").unique():
    print("=" * 100)
    csms = CrossSectionalMomentumStrategy(
        asset_class=asset_class, asset_class_history=data
    )
    csms.execute_backtest()
====================================================================================================
Equity Idx
calendar_year_return
Date
2002 0.099955
2003 0.175910
2004 0.076786
2005 0.200651
2006 0.306280
2007 0.417425
2008 -0.139295
2009 -0.365068
2010 -0.063702
2011 -0.007779
2012 -0.043413
2013 0.113152
2014 -0.019550
2015 0.138860
2016 -0.247295
2017 0.008662
2018 -0.214263
2019 -0.030656
2020 0.345217
Summary
Annualized Returns 0.019892
Annualized Volatility 0.163184
Sharpe Ratio (assuming $R_f$ = 0) 0.023065
====================================================================================================
Credit
calendar_year_return
Date
2002 0.062447
2003 -0.020326
2004 0.034811
2005 0.003829
2006 0.036200
2007 0.013033
2008 0.114485
2009 -0.156851
2010 0.038716
2011 -0.028698
2012 -0.006391
2013 0.057092
2014 -0.000328
2015 -0.002702
2016 0.027312
2017 -0.016831
2018 -0.009025
2019 0.041674
2020 0.004932
Summary
Annualized Returns 0.008773
Annualized Volatility 0.074868
Sharpe Ratio (assuming $R_f$ = 0) 0.038750
====================================================================================================
Govt Bond
calendar_year_return
Date
2002 0.014456
2003 -0.020465
2004 0.000196
2005 0.008041
2006 0.013660
2007 0.053238
2008 0.012984
2009 -0.079125
2010 0.031045
2011 -0.068423
2012 -0.023359
2013 -0.007865
2014 0.039288
2015 0.070262
2016 -0.044730
2017 0.049316
2018 -0.030712
2019 -0.015553
2020 -0.049961
Summary
Annualized Returns -0.003329
Annualized Volatility 0.097833
Sharpe Ratio (assuming $R_f$ = 0) -0.018946
====================================================================================================
Currencies
calendar_year_return
Date
2002 0.024058
2003 0.094440
2004 -0.013657
2005 -0.036125
2006 -0.001895
2007 0.000725
2008 0.099092
2009 -0.106025
2010 -0.049425
2011 -0.041039
2012 -0.032922
2013 0.090975
2014 -0.011465
2015 -0.009285
2016 0.059619
2017 0.029123
2018 -0.028743
2019 -0.014778
2020 -0.036071
Summary
Annualized Returns -0.000491
Annualized Volatility 0.070615
Sharpe Ratio (assuming $R_f$ = 0) -0.002142
====================================================================================================
Commod
calendar_year_return
Date
2002 -0.044172
2003 -0.148021
2004 0.005226
2005 0.168395
2006 0.228971
2007 0.049709
2008 0.356241
2009 -0.290566
2010 0.018869
2011 0.036266
2012 -0.201700
2013 0.165785
2014 0.110457
2015 0.035128
2016 -0.308718
2017 0.019870
2018 -0.112933
2019 -0.099779
2020 -0.442822
Summary
Annualized Returns -0.044557
Annualized Volatility 0.381381
Sharpe Ratio (assuming $R_f$ = 0) -0.053051
====================================================================================================
EQ Factors
calendar_year_return
Date
2002 0.000000
2003 -0.021777
2004 -0.035077
2005 0.023022
2006 -0.033745
2007 0.061733
2008 0.018270
2009 0.016557
2010 -0.011831
2011 -0.175542
2012 0.014983
2013 0.110600
2014 -0.079264
2015 0.058069
2016 -0.072724
2017 0.009679
2018 0.037166
2019 0.026270
2020 0.427873
Summary
Annualized Returns 0.014129
Annualized Volatility 0.228858
Sharpe Ratio (assuming $R_f$ = 0) 0.028603
time: 1.04 s