3. FuturesΒΆ

Investigate the mechanics and use of short selling S&P 500 futures contracts via the Chicago Mercantile Exchange (CME) as outlined in Document A of the case study.

The following investigation investigates futures prices of two contracts that would have been available during this time, i.e. ESH20 and ESM20, with maturities in March and June, respectively. The trading prices for these contracts were sourced from BarChart Premiere historical data. A literature review (where relevant) and coding examples have been provided as part of this analysis.

Key Assumptions & limitationsΒΆ

  • For the purposes of this assignment, we will assume that the evaluation date (i.e. the date in which Pershing Square must make a decision on their Hedging strategy) is February 21, 2020
  • Purchasing of futures on the Chicago Mercantile Exchange (CME)
  • Where discrepancies between Yahoo Finance share prices and derived share prices from Exhibit 3 were found, the Yahoo Finance share prices were adopted. Details are outlined further in 3.1

This investigation has been divided into four sections, as listed in the table of contents below:

Table of ContentsΒΆ

  • 3.1 Hedging Position and key details
  • 3.2 (Retrospective) Key Timings-Key-Timings)
  • 3.3 Profits
  • 3.4 Sensitivity Analysis
    • 3.4.1 Forecast Changes in Cost of Carry
  • 3.5 Acknowledgements and Tooling

Libraries

InΒ [Β ]:
import yfinance as yf
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.ticker as ticker
import numpy as np
from datetime import datetime

import warnings
warnings.filterwarnings("ignore")

3.1 Hedging Position and Key detailsΒΆ

To protect Pershing Square's portfolio from adverse market movements, they could utilise S&P 500 index futures contracts as a hedging instrument. This approach would allow the portfolio to gain protection against potential downside movements in the S&P 500

A futures contract is an agreement to buy or sell the underlying asset (in this case, the S&P 500 index) at a predetermined price on a specific date in the future. This allows the holder to lock in a price for the underlying asset, helping to protect against unfavorable price movements.

Ongoing Cashflows and CostsΒΆ

Whilst there are no upfront costs or regular payments like options and Credit Default Swaps (CDS) we must consider that an initial margin and maintenance margin must be maintained over the life of ownership of the asset. A coding example calculating the initial margin of this contract is demonstrated in section 3.1. The initial margin figure is sourced from CME risk data.

Since and observation of the payoff yield an immediate positive (and never a non-negative) payoff, we did not calculate any maintenance margin requires for these contracts.

Futures Contract PayoffΒΆ

The payoff of a futures contract at expiration depends on the relationship between the futures price ($S_F$) and the spot price of the underlying asset ($S_T$). If the spot price at expiration is higher than the agreed-upon futures price, the long position benefits, while the short position loses. Conversely, if the spot price is lower, the short position benefits.

The payoff of a short position futures contract is given by:

$$\text{Futures Contract Payoff} = K - S_T$$

Where:

  • $S_T$ is the spot price of the S&P 500 index at expiration.
  • $K$ is the agreed-upon futures price.

Determining the Optimal Number of ContractsΒΆ

To determine the optimal number of futures contracts required to hedge the portfolio, we can use the optimal hedging ratio. The goal is to align the portfolio's exposure to market movements with the performance of the hedging instrument (the S&P 500).

The optimal number of contracts $h$ can be calculated using the following formula:

$$h = \frac{\beta V}{F} = \bar{\rho} \frac{\bar{\sigma}_{A}V }{\bar{\sigma}_KF}$$

Where:

  • $h$ is the number of futures contracts to purchase or sell.
  • $\beta$ is the beta of the portfolio relative to the S&P 500 (this measures the portfolio's sensitivity to market movements).
  • $V$ is the value of the portfolio being hedged.
  • $F$ is the notional value of one futures contract on the S&P 500 index, calculated as the index value multiplied by the contract multiplier.
  • $\sigma _{A_t}$ is the standard deviation (volatility) in $A_t βˆ’ A$.
  • $\sigma _{K_t}$ is the standard deviation (volatility) in $K_t βˆ’ K$.
  • $\rho$ is the correlation between $A_t βˆ’ A$ and $K_t βˆ’ K$.

For the purposes of this assignment, $\sigma _{A_t}$, $\sigma _{K_t}$ and $\rho$ were calculated from historical daily returns in the prior four-year period (HISTORICAL_PRICE_DATE) to the evaluation date (EVAL_DATE).

InΒ [Β ]:
# Contract information
ESH20_CONTRACT_SIZE = 50
ESM20_CONTRACT_SIZE = 50
ESH20_HIST_PRICES = 'futures_data/esh20_price-history-09-24-2024.csv'
ESM20_HIST_PRICES = 'futures_data/esm20_price-history-09-24-2024.csv'
ESH20_MATURITY = '2020-03-30'
ESM20_MATURITY = '2020-06-30'
ESH20_INITIAL_MARGIN = 11_000.0
ESM20_INITIAL_MARGIN = 11_000.0

# Firm specific information
FIRM_VALUE = 7_621.28 * 1_000_000

# Contextual information
HISTORICAL_PRICE_DATE = '2016-01-04'
EVAL_DATE = '2020-02-21'
EVAL_DATE_PLUS_ONE = '2020-02-22'
EVAL_END_DATE = '2020-06-30'

Calculate the portfolio value, weights, historical returns and standard deviations.

This is a bit more complicated because the weighting for each firm in the portfolio varies as per the appendix provided on the assignment task sheet.

Process:

Import the historical prices for each firm, evaluate the firm value based upon the constituent assets owned in the portfolio and calculate the daily returns of the portfolio

InΒ [Β ]:
file_path = 'Perishing_portfolio.xlsx'
sheet_name = 'portfolio'
share_nums = {}

df = pd.read_excel(file_path, sheet_name=sheet_name)

for index, row in df.iterrows():
    ticker = row['Ticker Code']
    shares = row['Number of Shares']
    share_nums[ticker] = shares

# Also add the ^spx index
share_nums['^spx'] = 1

date_range = pd.date_range(start=HISTORICAL_PRICE_DATE, end=EVAL_END_DATE)
df = pd.DataFrame(index=date_range)

# stock prices for the evaluation period
for ticker, shares in share_nums.items():
    historical_data = yf.download(ticker, start=HISTORICAL_PRICE_DATE, end=EVAL_END_DATE)["Adj Close"]
    
    if not historical_data.empty:
        daily_values = historical_data * shares
        df[ticker] = daily_values

df = df.dropna(how='any')
df['Firm Value'] = df.loc[:, df.columns != '^spx'].sum(axis=1)
df_historic = df[df.index <= EVAL_DATE_PLUS_ONE]

df.index.name = 'Date'

p_returns = np.log(df_historic['Firm Value']).diff(1).dropna()
spx_returns = np.log(df_historic['^spx']).diff(1).dropna()

df_historic['Portfolio Returns'] = p_returns
df_historic['S&P 500 Returns'] = spx_returns
df_historic = df_historic.dropna()

# Portfolio price (we have discovered in previous sections)
p_price_eval_date = df_historic['Portfolio Returns'].loc[EVAL_DATE]

# Portfolio sigma
p_std_dev = np.std(p_returns)

# S&P 500 sigma
spx_std_dev = np.std(spx_returns)

# Safekeeping:
df_historic.to_excel('historic_portfolio_values.xlsx', sheet_name='Values')

df.head()
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
Out[Β ]:
CMG HLT LOW QSR BRK-B HHH A FNMA FMCC ^spx Firm Value
Date
2016-01-04 7.736718e+08 4.357516e+08 5.524744e+08 4.257521e+08 5.250389e+08 6.644987e+08 3.341809e+08 2.187775e+08 1.195375e+08 2012.660034 4.049683e+09
2016-01-05 7.740511e+08 4.359580e+08 5.536490e+08 4.094645e+08 5.270467e+08 6.575578e+08 3.330313e+08 2.227077e+08 1.202576e+08 2016.709961 4.033724e+09
2016-01-06 7.355062e+08 4.132306e+08 5.426402e+08 3.970094e+08 5.273680e+08 6.514694e+08 3.345095e+08 2.227077e+08 1.209777e+08 1990.260010 3.945419e+09
2016-01-07 7.171130e+08 3.977343e+08 5.289166e+08 3.879076e+08 5.199391e+08 6.362481e+08 3.203014e+08 2.161574e+08 1.173772e+08 1943.089966 3.841695e+09
2016-01-08 7.124415e+08 3.956681e+08 5.201833e+08 3.810812e+08 5.153212e+08 6.355175e+08 3.169340e+08 2.161574e+08 1.173772e+08 1922.030029 3.810681e+09

Calculate optimal portfolio

Note: The notional value and initial margin are the same as both contracts have the same contract size

InΒ [Β ]:
# Calculate rho
rho=np.corrcoef(df_historic['S&P 500 Returns'], df_historic['Portfolio Returns'])[0 ,1]

# Firm portfolio value and returns
beta = rho * spx_std_dev / p_std_dev

# optimal hedge ratio 
spx_price_eval_date = df_historic['^spx'].loc[EVAL_DATE]
F = spx_price_eval_date * ESM20_CONTRACT_SIZE # 
H = beta * FIRM_VALUE / F

# Calculate initial margin
margin_cost_esm20 = H * ESM20_CONTRACT_SIZE
margin_cost_esh20 = H * ESH20_CONTRACT_SIZE

Key Statistics

InΒ [Β ]:
stats = f'''
############ KEY STATISTICS ############
Standard deviation of portfolio from historical date ({HISTORICAL_PRICE_DATE}) to evaluation date ({EVAL_DATE}): {p_std_dev:.6f}
Standard deviation of S&P 500 Sigma from historical date ({HISTORICAL_PRICE_DATE}) to evaluation date ({EVAL_DATE}): {spx_std_dev:.6f}
Rho: {rho:.6f}
Beta: {beta:.6f}

Face value of futures contract on evaluation date ({EVAL_DATE}): ${F:.2f}


Optimal number of contracts to hedge firm position: {int(round(H, 0))} 
(unrounded {H:.2f})

############ INITIAL MARGIN ############
Initial Margin (ESM20): {margin_cost_esm20:.2f}
Initial Margin (ESH20): {margin_cost_esh20:.2f}
'''

print(stats)
############ KEY STATISTICS ############
Standard deviation of portfolio from historical date (2016-01-04) to evaluation date (2020-02-21): 0.010099
Standard deviation of S&P 500 Sigma from historical date (2016-01-04) to evaluation date (2020-02-21): 0.008079
Rho: 0.741110
Beta: 0.592861

Face value of futures contract on evaluation date (2020-02-21): $166887.50


Optimal number of contracts to hedge firm position: 27074 
(unrounded 27074.27)

############ INITIAL MARGIN ############
Initial Margin (ESM20): 1353713.70
Initial Margin (ESH20): 1353713.70

3.2 (Retrospective) Key TimingsΒΆ

Firstly, we will graphically illustrate the payoffs of the purchasing the contracts starting from the evaluation date to the contract maturity, i.e. 31st March and 30th June.

We will characterise the best exit timing of these securities as the date which would yield the maximum payoff between the evaluation date and maturity.

InΒ [Β ]:
import matplotlib.ticker as ticker
# Retrieve historic futures data for ESH20 and ESM20

def generate_futures_payoffs(historical_prices_wd, contract_size):
    
    futures_prices_df = pd.read_csv(historical_prices_wd, index_col='Time', parse_dates=['Time'])['Open'].to_frame()
    futures_prices_df = futures_prices_df[futures_prices_df.index >= EVAL_DATE]
    futures_prices_df.index = pd.to_datetime(futures_prices_df.index).strftime('%Y-%m-%d')
    futures_prices_df = futures_prices_df.sort_index()
    futures_prices_df = futures_prices_df.rename(columns={'Open': 'Price'})

    esh20_eval_date = futures_prices_df['Price'].loc[EVAL_DATE]

    futures_prices_df['Payoff from EVAL_DATE'] = (esh20_eval_date - futures_prices_df['Price']) * (H * contract_size)
    return futures_prices_df

esh20_prices_df = generate_futures_payoffs(ESH20_HIST_PRICES, ESH20_CONTRACT_SIZE)
esm20_prices_df = generate_futures_payoffs(ESM20_HIST_PRICES, ESM20_CONTRACT_SIZE)

plt.figure(figsize=(12, 6))
plt.plot(esh20_prices_df.index, esh20_prices_df['Payoff from EVAL_DATE'], linestyle='-', color='b', label='Hedging with ESH20 contracts')
plt.plot(esm20_prices_df.index, esm20_prices_df['Payoff from EVAL_DATE'], linestyle='-', color='r', label='Hedging with ESM20 contracts')
plt.title('Firm Value Perishing holding')
plt.xlabel('Date')
plt.ylabel('Firm Value ($M)')
plt.xticks(rotation=45)
plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=5))  
plt.gca().yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, pos: f'{x*1e-6:.1f}M'))
plt.grid()
plt.legend()
plt.tight_layout() 
plt.show()
No description has been provided for this image
InΒ [Β ]:
# The best date to close the futures contract with a reversing trade would be:
max_price_date = esm20_prices_df['Payoff from EVAL_DATE'].idxmax()

print(f'The best exit timing of the market would be {max_price_date.strftime('%Y-%m-%d')}.')
The best exit timing of the market would be 2020-03-23.

3.3 ProfitsΒΆ

We now consider the overall profits of Pershing Square maintaining their current portfolio and the futures hedging position.

The profit diagram of their firm is illustrated is the worked coding example below.

InΒ [Β ]:
# Given the payoff diagram above, we will only look at the ESM20 contract since the payoffs are similar and it gives up a longer time horizon.
df_profits = df[df.index >= EVAL_DATE].copy()  
df_profits = df_profits[['Firm Value']]

# Shuffling because the data isn't perfect
df_profits.index = pd.to_datetime(df_profits.index).normalize()
esm20_prices_df.index = pd.to_datetime(esm20_prices_df.index).normalize()
aligned_esm20_payoff = esm20_prices_df['Payoff from EVAL_DATE'].reindex(df_profits.index)

df_profits.loc[:, 'ESM Payoff'] = aligned_esm20_payoff

firm_value_eval_date = df_profits['Firm Value'].loc[EVAL_DATE]
df_profits['Firm Profit'] = df_profits['Firm Value'] - firm_value_eval_date
df_profits['Firm Profit With Futures Hedge'] = df_profits['ESM Payoff'] + df_profits['Firm Profit']
df_profits['Firm Value With Futures Hedge'] = df_profits['ESM Payoff'] + df_profits['Firm Value']

df_profits.head()
Out[Β ]:
Firm Value ESM Payoff Firm Profit Firm Profit With Futures Hedge Firm Value With Futures Hedge
Date
2020-02-21 7.696251e+09 0.000000e+00 0.000000e+00 0.000000e+00 7.696251e+09
2020-02-24 7.429246e+09 7.513111e+07 -2.670049e+08 -1.918738e+08 7.504377e+09
2020-02-25 7.145713e+09 1.996728e+08 -5.505380e+08 -3.508652e+08 7.345386e+09
2020-02-26 7.038965e+09 3.123694e+08 -6.572857e+08 -3.449162e+08 7.351335e+09
2020-02-27 6.684721e+09 3.631337e+08 -1.011530e+09 -6.483962e+08 7.047855e+09
InΒ [Β ]:
plt.figure(figsize=(12, 6))
plt.plot(df_profits.index, df_profits['Firm Value With Futures Hedge'], linestyle='-', color='b', label='Firm Value with futures hedging strategy')
plt.plot(df_profits.index, df_profits['Firm Value'], linestyle='-', color='r', label='Firm Value without futures hedging strategy')
plt.title('Perishing Square Value and use of Futures Hedging')
plt.xlabel('Date')
plt.ylabel('Firm Value ($M)')
plt.xticks(rotation=45)
plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=10))  
plt.gca().yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, pos: f'{x*1e-6:.1f}M'))
plt.grid()
plt.legend()
plt.tight_layout() 
plt.show()
No description has been provided for this image

3.4 Sensitivity AnalysisΒΆ

A futures contract can be priced using the cost of carry approach

$$K = Se^{(r+sβˆ’q)T}$$ (using Compound Interest)

i.e. that the futures price is dependent on the risk-free interest rate $r$, storage cost $c$ and dividend yield $q$ in some period $T$.

3.4.1 Forecast Changes in Cost of CarryΒΆ

The following code example forecasts changes in the estimated cost of carry:

  • The cost of carry is first derived as of the evaluation date (February 21, 2020).
  • The payoffs are then evaluated under different cost of carry values, considering forecast periods of one week, one month, and two months into the future.
InΒ [Β ]:
eval_date_as_date = datetime.strptime(EVAL_DATE, '%Y-%m-%d')
esm20_maturity_as_date = datetime.strptime(ESM20_MATURITY, '%Y-%m-%d')

T = (esm20_maturity_as_date - eval_date_as_date).days / 360

K = esm20_prices_df['Price'].loc[EVAL_DATE]
S = spx_price_eval_date

cost_of_carry = np.log((K / S))* (1 / T)

print(f'K: {K} \nS: {S}\nT: {T}\nCost of Carry: {cost_of_carry:3f}')
K: 3367.5 
S: 3337.75
T: 0.3611111111111111
Cost of Carry: 0.024573
InΒ [Β ]:
min_value = cost_of_carry - 0.25
max_value = cost_of_carry + 0.1

T_after_one_week = ((esm20_maturity_as_date - eval_date_as_date).days - 7) / 360
T_after_one_month = ((esm20_maturity_as_date - eval_date_as_date).days - 30) / 360
T_after_two_months = ((esm20_maturity_as_date - eval_date_as_date).days - 60) / 360

sensitivity_values = np.linspace(min_value, max_value, 50)
df_sensitivity = pd.DataFrame(sensitivity_values, columns=['Cost Of Carry'])
df_sensitivity['K One Week Forecast'] = S * np.exp(df_sensitivity['Cost Of Carry'] * T_after_one_week)
df_sensitivity['K One Month Forecast'] = S * np.exp(df_sensitivity['Cost Of Carry'] * T_after_one_month)
df_sensitivity['K Two Month Forecast'] = S * np.exp(df_sensitivity['Cost Of Carry'] * T_after_two_months)

df_sensitivity.head()
Out[Β ]:
Cost Of Carry K One Week Forecast K One Month Forecast K Two Month Forecast
0 -0.225427 3090.324643 3135.154350 3194.606642
1 -0.218284 3097.875717 3141.381070 3199.046678
2 -0.211141 3105.445242 3147.620157 3203.492885
3 -0.203998 3113.033263 3153.871634 3207.945272
4 -0.196855 3120.639824 3160.135528 3212.403847
InΒ [Β ]:
plt.figure(figsize=(10, 6))
plt.plot(df_sensitivity['Cost Of Carry'], df_sensitivity['K One Week Forecast'], label='One Week Forecast')
plt.plot(df_sensitivity['Cost Of Carry'], df_sensitivity['K One Month Forecast'], label='One Month Forecast')
plt.plot(df_sensitivity['Cost Of Carry'], df_sensitivity['K Two Month Forecast'], label='Two Month Forecast')
plt.title('Sensitivity Analysis of Cost of Carry on Future Forecasts')
plt.xlabel('Cost of Carry')
plt.ylabel('Forecasted Value (K)')
plt.legend()
plt.grid(True)


plt.tight_layout()
plt.show()
No description has been provided for this image

3.5 Acknowledgements and ToolingΒΆ

This work is licensed under the MIT License and is freely available for distribution. All rights are reserved by the author, DanielCiccC.

  • Various tools, including GitHub, GitHub Copilot, and ChatGPT, were utilised in the development and analysis of this project.
  • Portions of the code were adapted from examples provided in lectures.
LICENSE
MIT License

Copyright (c) 2024 DanielCiccC

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.