<jupyter_start><jupyter_text>Sector Rotation Strategy<jupyter_code>import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import yfinance as yf

list_of_sector_etfs = [
    "XLK", # SPDR Technology Sector Fund
    "XLY", # SPDR Consumer Discretionary Sector Fund
    "XLF", # SPDR Financial Sector Fund
    "XLI", # SPDR Industrial Sector Fund
    "XLV", # SPDR Health Care Sector Fund
    "XLP", # SPDR Consumer Staples Sector Fund
    "XLE", # SPDR Energy Sector Fund
    "XLB", # SPDR Materials Sector Fund
    "XLU", # SPDR Utilities Sector Fund
    "IYR", # iShares U.S. Real Estate
]

def sharpe_ratio(r):
    return np.sqrt(12) * r.mean() / r.std()

def get_return(x):
    return x*100 - 100

# download the data from yfinance API

sector_df = yf.download(" ".join(list_of_sector_etfs))['Adj Close'].dropna()
spy = yf.download("SPY")['Adj Close']<jupyter_output>[*********************100%***********************]  10 of 10 completed
[*********************100%***********************]  1 of 1 completed<jupyter_text>Strategy Results Here we test two different Sector Rotation strategies, one based on picking the minimum variance sector ETF and holding for one month, and another based on picking the sector ETF with the highest one year return and holding for one month. We see that both strategies out perform in terms of returns but the minimum variance strategy provides a better sharpe ratio (risk-reward ratio).<jupyter_code>min_variance_mask = (sector_df.pct_change().rolling(63).var().rank(axis=1).resample('1m').last() == 1)
min_variance_mask = (min_variance_mask.div(min_variance_mask.sum(axis=1),axis=0)).dropna()
min_variance_returns = (sector_df.resample('1m').last().pct_change()*min_variance_mask.shift()).sum(axis=1)

momentum_mask = ((-sector_df.resample('1m').last().pct_change(11)).rank(axis=1) == 1)
momentum_mask = (momentum_mask.div(momentum_mask.sum(axis=1),axis=0)).dropna()
momentum_returns = (sector_df.resample('1m').last().pct_change()*momentum_mask.shift(2)).sum(axis=1)

start_index = max(momentum_returns.index[0], min_variance_returns.index[0])

get_return((1+min_variance_returns)[start_index:].cumprod()).plot(figsize=(16,4),
                                                label='Min Variance Based Sector Rotation')
get_return((1+momentum_returns)[start_index:].cumprod()).plot(figsize=(16,4),
                                                label='Momentum Based Sector Rotation')
get_return((1+spy.resample('1m').last().pct_change()[start_index:]).cumprod()).plot(figsize=(16,4),
                                                label='SPY')
plt.title("Strategy Performance")
plt.ylabel("Return")
plt.legend()
plt.show()

print("Min Variance Strategy Sharpe: ", np.round(sharpe_ratio(min_variance_returns[start_index:]),2))
print("Momentum Strategy Sharpe: ", np.round(sharpe_ratio(momentum_returns[start_index:]),2))
print("SPY Sharpe: ", np.round(sharpe_ratio(spy.resample('1m').last().pct_change()[start_index:]),2))<jupyter_output><empty_output><jupyter_text>The above strategy has a lack of diversification and therefore could carry unwanted risk because it is fully invested in a single sector at any given point. Below we try the same strategy but instead of holding just the top ranking sector, we hold the top three ranking sectors. We see that the total return decreases for both the minimum variance and the momentum based sector rotation strategy. This may be expected for a more diversified strategy but what is interesting is that the sharpe ratio also goes down.<jupyter_code>min_variance_mask = (sector_df.pct_change().rolling(63).var().rank(axis=1).resample('1m').last() <= 3)
min_variance_mask = (min_variance_mask.div(min_variance_mask.sum(axis=1),axis=0)).dropna()
min_variance_returns = (sector_df.resample('1m').last().pct_change()*min_variance_mask.shift()).sum(axis=1)

momentum_mask = ((-sector_df.resample('1m').last().pct_change(11)).rank(axis=1) <= 3)
momentum_mask = (momentum_mask.div(momentum_mask.sum(axis=1),axis=0)).dropna()
momentum_returns = (sector_df.resample('1m').last().pct_change()*momentum_mask.shift(2)).sum(axis=1)

start_index = max(momentum_returns.index[0], min_variance_returns.index[0])

get_return((1+min_variance_returns)[start_index:].cumprod()).plot(figsize=(16,4),
                                                label='Min Variance Based Sector Rotation')
get_return((1+momentum_returns)[start_index:].cumprod()).plot(figsize=(16,4),
                                                label='Momentum Based Sector Rotation')
get_return((1+spy.resample('1m').last().pct_change()[start_index:]).cumprod()).plot(figsize=(16,4),
                                                label='SPY')
plt.title("Strategy Performance")
plt.ylabel("Return")
plt.legend()
plt.show()

print("Min Variance Strategy Sharpe: ", np.round(sharpe_ratio(min_variance_returns[start_index:]),2))
print("Momentum Strategy Sharpe: ", np.round(sharpe_ratio(momentum_returns[start_index:]),2))
print("SPY Sharpe: ", np.round(sharpe_ratio(spy.resample('1m').last().pct_change()[start_index:]),2))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.15396669931439766, 'fraction_numerical': 0.0188050930460333, 'mean_word_length': 4.944121071012805, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 12, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 15, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '3', 'dir': 'jupyter-structured-clean-dedup', 'id': '644246', 'license': 'MIT', 'n_tokens_mistral': 1709, 'n_tokens_neox': 1597, 'n_words': 350, 'path': 'SectorRotation.ipynb', 'repo_name': 'andrew-dusa/SectorRotation', 'size': '5105'}