Nasdaq vs S&P500

Trading Hypothesis: If Interest Rates Rise, Technology Companies valuations will be impacted worse than the broader stock market. 

Why? – You should complete your own historical analysis on market analysts deflation on valuations of different company sectors based on historical rate hikes.This is not investment advice! But let’s say I do my research and I conclude: 

Technology companies are more sensitive to interest rates due to the influence of current DCF valuations based on the expectation of high growth rates in free cash flow in the future. 

How could I benefit from this view?

The NASDAQ-100 Index is heavily weighted to the technology sector. The S&P 500 Index, by contrast, is recognized as having a broad, diversified constituency and represents the broad market. What makes this type of trade possible is both of these indices, while slightly different, have a high degree of price correlation.

import numpy as np
import pandas as pd 
import datetime as dt
import matplotlib.pyplot as plt
from pandas_datareader import data as pdr

def get_data(stocks, start, end):
    stockData = pdr.get_data_yahoo(stocks, start, end)
    stockData = stockData['Close']
    log_returns = np.log(stockData) - np.log(stockData.shift(1))
    covMatrix = log_returns.cov()
    corMatrix = log_returns.corr()
    return stockData, log_returns, covMatrix, corMatrix

indices = ['^GSPC', '^IXIC']
endDate = dt.datetime.now()
startDate = endDate - dt.timedelta(days=1000)
prices, log_returns, covMatrix, corMatrix = get_data(indices, startDate, endDate)
prices['diff'] = prices['^IXIC'] - prices['^GSPC']

plt.plot(prices)
plt.title('Correlation between S&P500 and Nasdaq')
plt.xlabel('S&P500')
plt.ylabel('Nasdaq')
plt.legend(['S&P500', 'Nasdaq', 'diff'])
plt.show()

print('stats\n',prices.describe(), '\n\nlast price diff',prices['diff'][-1])

df = prices['diff']
normalized_df=(df-df.mean())/df.std()
# to use min-max normalization:
mm_normalized_df=(df-df.min())/(df.max()-df.min())

# plt.plot(normalized_df)
plt.figure(figsize=(12,6))
plt.plot(mm_normalized_df)

plt.scatter(log_returns['^GSPC'], log_returns['^IXIC'])
plt.title('Correlation between S&P500 and Nasdaq')
plt.xlabel('S&P500')
plt.ylabel('Nasdaq')
plt.show()
print('Index Correlation: ', corMatrix)
print('Index Covariance to Volatilty: ', np.sqrt(covMatrix)*np.sqrt(252))
print('Index Volatility: ', np.std(log_returns)*np.sqrt(252))

Trading Opportunities in Equity Index Futures

https://www.cmegroup.com/education/whitepapers/stock-index-spread-opportunities.html

https://www.cmegroup.com/education/courses/introduction-to-equity-index-products/trading-opportunities-in-equity-index-futures.html

In order to construct this spread between Index Futures, we must first calculate a Spread Ratio. The spread ratio is defined as the notional value of one index future divided by the notional value of another.

In this case, we will divide the notional value of the NASDAQ-100 futures by the notional value of the S&P 500 futures.

Executing the Trade

Example: A portfolio manager (PM) believes the tech sector is at risk versus the broad market. He is willing to express this opinion with a $10 million equivalent risk position, leading the PM to take the following actions:

  • The PM sells the E-mini NASDAQ-100/E- mini S&P 500 spread
# Parameters
SP500 = 4373.94
NASD = 	13751.40

emini_SP500_x = 50 # $50 per point
emini_NASD_x = 20 # $20 per point

emini_SP500 = SP500*emini_SP500_x
emini_NASD = NASD*emini_NASD_x

spread_ratio = emini_NASD/emini_SP500

print('Notational Value of E-mini S&P500: ', round(emini_SP500,0))
print('Notational Value of E-mini NASDAQ: ', round(emini_NASD,0))
print('Spread Ratio: ', round(spread_ratio,2))

print('Portfolio Postion: $', 10000000,'\n')

print('Short Postion in E-mini NASDAQ: ', int(10000000/emini_NASD))
print('Long Position in E-mini S&P500: ', int(spread_ratio*10000000/emini_NASD))

OTHER OPTION: OTC Spread Options on Indices

One of the main uses of Monte Carlo simulation is for pricing options under multiple stochastic factors.
Pricing options whose pay-off depends on multiple asset prices, or with stochastic volatility. Let’s consider a European spread option on the difference between two assets (stock indices) $S_1$ and $S_2$ which follow a GBM process.
$dS_1 = (r-\delta_1)S_1dt + \sigma_1S_1dz_1$
$dS_2 = (r-\delta_2)S_2dt + \sigma_2S_2dz_2$

# Parameters
SP500 = 4373.94
NASD = 	13751.40
div_SP500 = 0.0127
div_NASD = 0.0126
vol_SP500 = 0.143050
vol_NASD = 0.194692
K = 9377 # current difference between index points 
T = 1
r = 0.01828 # 10yr US Treasury Bond
rho = 0.922323 # correlation
N = 1 # discrete time steps
M = 1000 # number of simulations

Slow Steps

# Precompute constants
dt = T/N
S1 = NASD
S2 = SP500
nu1dt = (r - div_NASD - 0.5*vol_NASD**2)*dt
nu2dt = (r - div_SP500 - 0.5*vol_SP500**2)*dt
vol1sdt = vol_NASD*np.sqrt(dt)
vol2sdt = vol_SP500*np.sqrt(dt)
srho = np.sqrt(1-rho**2)

# Standard Error Placeholders
sum_CT = 0
sum_CT2 = 0

# Monte Carlo Method
for i in range(M):
    St1 = NASD
    St2 = SP500
    
    for j in range(N):
        dz1 = np.random.normal()
        dz2 = np.random.normal()
        z1 = dz1
        z2 = rho*dz1 + srho*dz2
        St1 = St1*np.exp(nu1dt + vol1sdt*z1)
        St2 = St2*np.exp(nu2dt + vol2sdt*z2)
    
    CT = max(0, K - (St1 - St2))
    
    sum_CT = sum_CT + CT
    sum_CT2 = sum_CT2 + CT*CT

# Compute Expectation and SE
C0 = np.exp(-r*T)*sum_CT/M
sigma = np.sqrt( (sum_CT2 - sum_CT*sum_CT/M)*np.exp(-2*r*T) / (M-1) )
SE = sigma/np.sqrt(M)

print("Call value is ${0} with SE +/- {1}".format(np.round(C0,2),np.round(SE,2)))

Fast Solution

# Precompute constants
N=100
dt = T/N
S1 = NASD
S2 = SP500
nu1dt = (r - div_NASD - 0.5*vol_NASD**2)*dt
nu2dt = (r - div_SP500 - 0.5*vol_SP500**2)*dt
vol1sdt = vol_NASD*np.sqrt(dt)
vol2sdt = vol_SP500*np.sqrt(dt)
srho = np.sqrt(1-rho**2)

# Monte Carlo Method
dz1 = np.random.normal(size=(N, M)) 
dz2 = np.random.normal(size=(N, M)) 
Z1 = dz1
Z2 = rho*dz1 + srho*dz2
delta_St1 = nu1dt + vol1sdt*Z1
delta_St2 = nu2dt + vol2sdt*Z2
ST1 = S1*np.cumprod( np.exp(delta_St1), axis=0)
ST2 = S2*np.cumprod( np.exp(delta_St2), axis=0)
ST1 = np.concatenate( (np.full(shape=(1, M), fill_value=S1), ST1 ) )
ST2 = np.concatenate( (np.full(shape=(1, M), fill_value=S2), ST2 ) )

CT = np.maximum(0, K - (ST1[-1] - ST2[-1]))
C0 = np.exp(-r*T)*np.sum(CT)/M

sigma = np.sqrt( np.sum( (np.exp(-r*T)*CT - C0)**2) / (M-1) )
SE= sigma/np.sqrt(M)

print("Call value is ${0} with SE +/- {1}".format(np.round(C0,2),np.round(SE,3)))