On Sunday September 2, 1666 a small fire started on the premises of the King's baker in London. By Thursday, the resulting inferno--later dubbed the Great Fire--had reduced much of the medieval City of London to ash. Writing of the fire in his diary, Samuel Pepys described a catastrophic scene of almost biblical proportions: "the conflagration was so universal…there was nothing heard or seen but crying out and lamentation, running about like distracted creatures without at all attempting to save even their goods, such a strange consternation there was upon them."
Seventy thousand people lost their homes in the blaze and 15% of the city's housing was destroyed.
In the wake, of the disaster, economist Nicholas Barton and others established the Insurance Office for Houses, the first fire insurance company, and the grandfather of today's property insurers (yes you can thank the Great Fire of London for all those Geico commercials). Nicholas Barton, his colleagues, and his clients understood intuitively the value proposition of insurance: spend a little now to avoid and potentially reverse a devastating calamity in the future. Insurance took off.
An industry was born, and by 1752, the idea had spread further than the original fire ever could have, when Benjamin Franklin started the Philadelphia Contributionship for the Insurance of Houses from Loss by Fire (because in the 18th century, why use one word when you can use ten). Not only did the insurance companies start fire departments and prevention efforts to limit the number of fires in the first place, but they also diversified risk and, most importantly from the insured's perspective, guaranteed to replace all or a portion of wealth lost in a fire. The impact of one bad event could be negated for a small fee during good years, ultimately leaving the insured ahead in the long-run.
In many ways, this logic describing the beneficial impact of insurance lies at the heart of Safe Haven, the book by hedge fund iconoclast Mark Spitznagel that we've explored in our last two blog posts. In our first post, we developed Spitznagel's idea of a cost-effective risk-mitigation strategy. We saw that a given investment's contribution to a portfolio can be analyzed along two dimensions: the asset's contribution to the portfolio's arithmetic average return and the asset's contribution to the portfolio's geometric average return. In general, although a risk-mitigation strategy will lower the arithmetic average return for a portfolio, a cost-effective strategy will actually increase the geometric average return for the portfolio. That is, you can reduce risk and increase returns simultaneously. It's a financial free lunch, one where you can have your cake and eat it too (please eat responsibly).
We're going to explore in further depth what forms those cost-effective risk-mitigation strategies can take and show you how to find your own safe haven.
This article provides an extension of some of the same themes and concepts we saw in our last article about Mark Spitznagels' excellent book, Safe Haven: Investing for Financial Storms. (Seriously check out his book). Previously we've looked at safe havens for dice games. But now we apply those same mechanics to the S&P 500, again coming to the conclusion that an insurance safe haven is the ideal addition to a portfolio. By providing the most bang for its buck, an insurance safe haven can decrease risk and increase returns. If you're looking to explore these concepts in more depth and automate your trading without having to code, check out the free demo of our no-code algorithmic trading platform here.
What makes a Safe Haven?
If you've been around trading and investing long enough you've probably heard terms like "crisis alpha" or "flight to safety" to describe assets that purportedly perform well when markets are crashing. For example, stocks and bonds have been inversely correlated (one goes up while the other goes down) which provides investors some cover when markets begin to crash. Others are uncorrelated to the market and are designed to perform regardless of what financial markets do.
Spitznagel proposes an experiment. Say we have three types of safe haven assets: alpha, store of value, and insurance, each with different payoffs. We allocate part of our capital to a safe haven and part of it to a risk asset - namely the S&P 500. If we run a Monte Carlo simulation sampling from historical returns - what gives us the best performance in terms of our CAGR?
If you read our previous post on the subject, we did something similar by playing dice and saw that insurance had a fascinating effect - it increased the growth of our portfolio despite having an expectation of 0 for the gambler.
You probably see where this is going; adding a cost-effective insurance policy to your portfolio is going to boost your returns and reduce your risk.
And that's exactly what Spitznagel shows.
The plot above was re-created from the book where we have the diagonal line x=y being the returns of the S&P 500. Anything below that line underperforms the S&P 500 in terms of compounding, even though it may appear better if you take the simple average of the returns which is caused by heavy skew towards the few lucky winners. We see here that insurance meets Spitznagel's definition of a cost-effective risk mitigation strategy because although it reduces the arithmetic mean, it raises the geometric mean of the returns meaning more actual money in your pocket.
Spitznagel provides all the numbers up front, and they're set up in a way to illustrate the conclusion he wants to make. There's nothing wrong with that. He calls the payoffs "cartoons" and wants to lead the reader along to demonstrate his perspective in a simplified manner for pedagogical reasons. If you want to recreate this, then you're on your own.
In fact, we could do that here - that would be the easy thing - but you should just get his book instead and enter into his thought process (he's a much better writer than we are anyway).
What we're going to do instead is reverse engineer his process to show how it works to give an idea how you can find your own insurance-based safe haven.
Get your favorite flavour of Python and let's dive into some data.
Finding a Cost-Effective Safe Haven
Spitznagel is looking at the total return data from 1901-2020 in the S&P 500: we'll do the same.
While the S&P index itself only goes back to 1923, with its current, 500-stock form coming into play in 1957, Spitznagel's data goes all the way back to 1901. We don't know exactly where he gets his data, but we do know that Nobel laureate Robert Shiller maintains a regularly updated total real-return index stretching back to 1871. We'll grab this for our risk portfolio with the following code:
import numpy as np import pandas as pd import matplotlib.pyplot as plt np.random.seed(1234) url = "http://www.econ.yale.edu/~shiller/data/ie_data.xls" df = pd.read_excel(url, sheet_name='Data', skiprows=7) # Just keep date and price columns df = df[['Date', 'Price.1']] df.columns = ['date', 'real_total_return_price'] # Drop nan at the end of the table df.drop(df.index[-1], axis=0, inplace=True)
Now that we have some data to work with, we need to turn it into years for sampling. We'll follow Spitznagel here and break our data into annual buckets based on the returns. What we'll do (which he doesn't but recommends) is give overlapping years.
For example, he has 120 years of data, all running January-December, which may introduce a bias into the data if there is a January effect in the historical data making that a particularly good time to buy. Instead, we'll take our 120 years and make our years go January-December, February-January, March-February, and so on giving us 1,440 years to sample from and hopefully reducing some of that potential bias in the data. We'll do that step here and cut down our data to the same 120 year period:
df['returns'] = df['real_total_return_price'].pct_change() df['log_returns'] = np.log(1 + df['returns']) df['annual_returns'] = np.exp(df['log_returns'].rolling(12).sum()) - 1 df['date'] = df['date'].astype(str) df['year'] = df['date'].map(lambda x: int(x.split('.'))) # Reduce to 1901-2020 df_rets = df.loc[(df['year']>1900) & (df['year']<2021)][['date', 'year', 'annual_returns']].copy() df_rets.reset_index(inplace=True) df_rets.drop('index', inplace=True, axis=1) df_rets.head()
For Spitznagel's example, he provides cartoon returns for his insurance policy. It pays off only if the stock index turns in a terrible year where it loses 15% or more, otherwise you lose your entire premium. Right now, we'll stick with his model to show how it works before we relax this with a more realistic example.
So, we bucket the index returns into 15% buckets from -15% to +30% and plot it with the following code:
# Add bin mapping to match Spitznagel's groupings bins = [-10, -0.15, 0, 0.15, 0.3, 10] categories = [0, 1, 2, 3, 4] labels = ['<-15%', '-15% to 0%', '0% to 15%', '15% to 30%', '>30%'] cats = pd.cut(df_rets['annual_returns'], bins, labels=categories) labs = pd.cut(df_rets['annual_returns'], bins, labels=labels) df_agg = pd.concat([df_rets, cats, labs], axis=1) df_agg.columns = ['date', 'year', 'returns', 'category', 'label'] df_agg['category'] = df_agg['category'].astype(int) # Plot the results colors = plt.rcParams['axes.prop_cycle'].by_key()['color'] fig, ax = plt.subplots(figsize=(12, 8)) ax.bar(categories, df_agg['category'].value_counts().sort_index(), color=colors) ax.set_xticks(categories) ax.set_xticklabels(labels) plt.xlabel('Annual S&P 500 Return (%)') plt.ylabel('Frequency') plt.title('Annual S&P 500 Returns from 1901-2020') plt.xticks(rotation=45) plt.tight_layout() plt.show()
The results are fairly close to what we see in the book, just more samples and more years with big losses. With this, we can figure out the break even payoff for insurance.
The break even payoff is the payoff where buying insurance yields an expectation of zero for both the insurance company and the buyer. This is easy to calculate in this scenario, it's just 1 divided by the probability of the event we're trying to insure against (i.e. 15%+ annual loss). In our data set, we have 167/1,440 years with a big loss, or 11.6%. The inverse of that gives us a 762% payoff from our insurance policy, assuming we lose our entire premium in every other case (-100% return).
For the insurance company, their relevant expectation is the arithmetic mean of the returns because they can spread their bets out across a broad group of people. If the mean of this payoff profile drops below 0, they won't offer insurance at that price, so this is the maximum payoff we can expect to receive from insuring our portfolio - barring any systematic mis-pricing of risk.
If you have the book, you'll note that Spitznagel's data gives him a higher payoff of 1000% - which is the break even payoff - because his S&P 500 data only lands in the 15% loss bucket 9% of the time, and 1/0.09 comes out to be ~11 for a 1,000% return.
Now it's time to tie this into our S&P 500 Monte Carlo simulation!
Simulating the S&P Stocks
To simulate our returns, we'll use a technique called bootstrap sampling or bootstrapping. This just means we take all of our sample data and randomly pick a year to add it to our returns, then put that year back and do it again (i.e. sampling with replacement). This is a rather simple technique and works well for this situation because we're not making any predictions, relying on any indicators, or doing any backtesting, all of which may be dependent on specific time series. Instead, we're looking to learn about the statistics of particular portfolios that we could simply buy and hold.
The code for this is easy from this point. We have our data frame with our years and the particular category we mapped it to. So, we can just sample from the index of that data frame to obtain vectors for both our annual returns and our insurance payoffs.
Here's the code for that:
def sp500Sim(data: pd.DataFrame, allocation: float=0, payoffs: list=[8.62, 0, 0, 0, 0], years: int=25, samples: int=10000): payoffs = np.asarray(payoffs) sims = np.random.choice(data.index, size=(samples, years)) ret_cats = data['category'].values[sims] # Calculate returns risk_rets = (1 - allocation) * (data['returns'].values[sims] + 1) safe_haven_rets = allocation * payoffs[ret_cats] return np.cumprod(risk_rets + safe_haven_rets, axis=1)
This function takes our data frame, insurance payoff profile, and how much of our portfolio we're willing to add to our safe haven as arguments, and spits out the cumulative compounding returns for analysis. If we set allocation = 0, as we have by default, we'll run a pure S&P 500 strategy. Let's do that first just to get a baseline.
traj = sp500Sim(df_agg, allocation=0)
Using some of the plotting functions developed in our previous post, we can visualize the output.
Without any safe haven allocation, we compound at 6.7% per year, and have an arithmetic mean of 8.6%.
Before jumping to the insurance case, we need to know how much insurance to allocate. We'll run a brute force search of all of our possible allocations from 0-20% to see how the curves evolve (typically, go 0-100% first, but I'm skipping that step to get to the point). We could be more clever about this, but a few million simulations like this is easy and won't take too long.
For this, we'll loop over the function above changing the allocation and calculating the results.
alloc_frac = np.linspace(0, 0.20, 101) N = 10 # Could make these 3D... vals5 = np.zeros((len(alloc_frac), N)) vals50 = vals5.copy() vals95 = vals5.copy() for i in range(N): for j, f in enumerate(alloc_frac): traj = sp500Sim(df_agg, allocation=f) perc5, _ = getQuantilePath(traj, 0.05) perc50, _ = getQuantilePath(traj, 0.5) perc95, _ = getQuantilePath(traj, 0.95) vals5[j, i] += perc5 vals50[j, i] += perc50 vals95[j, i] += perc95 # Average our sample medians to smooth out the plot smooth5 = vals5.mean(axis=1) smooth50 = vals50.mean(axis=1) smooth95 = vals95.mean(axis=1)
For our 5th and 50th percentile, we see that our optimal allocation comes in at ~3.5%. Let's take that and run it through our simulation again to look at the compounding effects to see if we get a lift versus our baseline.
ins_traj = sp500Sim(df_agg, allocation=0.035)
And just like that, we've boosted our CAGR from 6.7% to 7.1% while sacrificing our arithmetic mean by lowering it from 8.6% to 8.2%.
Additionally, notice that we've also reduced our risk while boosting our returns with a product that has an expectation of 0 (something modern finance says is impossible).
Take the 5th percentile outcome. If you're unlucky and wind up down here, that little bit of insurance leaves you with a 2.5% CAGR. If you're unlucky without insurance, your CAGR is down to -0.1%.
An ounce of precaution prevented a whole lot of portfolio purchasing power erosion!
Also, the same insurance allocation gives you the best chance at maximizing the median and maximizing the 5th percentile. So you have minimized your downside with this allocation.
Hopefully you're seeing the power of this method of investing.
How much to Pay for Insurance?
As stated above, this 762% payoff from your insurance policy is an upper bound, a best case scenario for investors trying to buy protection. In reality, there are going to be some other costs that will bring that payoff down so that each $1 you spend on insurance is going to only yield say, $6 of return. We can use this same approach to estimate when insurance gets too expensive for us and we're better off looking for another safe haven or foregoing it all together.
In the code below, we are going to range our allocations from 0-20% and go down to a 431% payoff to find where that decision boundary exists between cost-effective insurance and insurance that's too expensive. As the price of insurance changes, so does our optimal allocation, so we now have to run a lot of simulations to get the relevant data points!
alloc_frac = np.linspace(0, 0.20, 41) ins_cost0 = 8.62 ins_cost = np.linspace(0.5, 1, 51) N = 20 # Multiple runs to smooth out results vals5 = np.zeros((len(alloc_frac), len(ins_cost), N)) vals50 = vals5.copy() vals95 = vals5.copy() for n in range(N): for i, c in enumerate(ins_cost): payoffs = [ins_cost0 * c, 0, 0, 0, 0] for j, f in enumerate(alloc_frac): traj = sp500Sim(df_agg, allocation=f, payoffs=payoffs) perc5, _ = getQuantilePath(traj, 0.05) perc50, _ = getQuantilePath(traj, 0.5) perc95, _ = getQuantilePath(traj, 0.95) vals5[j, i, n] += perc5 vals50[j, i, n] += perc50 vals95[j, i, n] += perc95
This plot shows where insurance is going to boost our portfolio's returns in the 5th and 50th percentile cases. The red lines indicate the CAGR from the optimal allocation sbecause your optimal allocation changes as the price of insurance changes. This should make intuitive sense. As insurance gets cheaper, you're getting a better and better deal so ought to buy more of it, thus raising your allocation total.
The dashed black line shows the CAGR for the un-hedged portfolio. The vertical dashed line indicates the break-even point for the 50th percentile case; any insurance return to the right of this is cost-effective, and any payoff to the left is a net drag on your portfolio. This value comes in at 680%, so as long as your payoff is greater than that, you're clearly improving your portfolio by adding a bit of it to the mix.
For the 5th percentile case, you can pay up for insurance and greatly limit your downside. Of course, any capital that goes to expensive insurance is going to limit your upside as well. There's no unambiguous "best case" if you choose to over-pay for insurance because you can still improve your situation if you get unlucky, but you're no longer also improving your most likely outcome.
Let's recap what we've done so far.
We started with wanting to invest in the S&P 500 and decided to determine if we could offset some of the risk with insurance. We got our data and came up with probabilities of outcomes for the S&P 500's annual returns, then figured out what a best-case scenario insurance contract would look like. From there, we used bootstrapping to find the optimal allocation for portfolio insurance and saw how a little bit of insurance gave us a healthy boost to our returns and massively reduced our downside risk. Finally, we wanted to see where that cost-effective boundary lies with respect to the insurance contract. This data gives us a measuring stick that we can take into the market to determine what products are worth adding to our portfolio.
Of course, this assumes that you can call up some bank or insurance company to buy a product that would protect you if the S&P 500 took a 15% dive in a given 12-month period.
Where to Find a Safe Haven?
In the final chapter of the book, Spitznagel goes through the same bootstrapping process we did, but looks at gold, treasuries, and CTAs instead of insurance.
Gold comes closest to performing as a safe haven asset, but when you pick apart the numbers, you find that its performance is heavily skewed by the 1970's when inflation was roaring (maybe something we're starting to see now).
There are safe havens out there, as Spitznagel attests to the returns his fund has made, but he doesn't tell us where to look, but the obvious answer seems to be found in the options market because these are contracts that most closely resemble our cartoon insurance payoff.
We have data that gives us the probability of a 15% market correction over any 12 month period. Using that value and the implied volatility of SPY puts, we can monitor for times when insurance looks cheap (or I should say, cost effective) to purchase a 12 month put. We know that this will expire worthless most of the time - just like in our cartoon insurance example - but we expect it to hit frequently enough that we can reduce our risk and increase our overall performance.
Options as Potential Safe Havens
Let's go over options 101 for anyone who isn't familiar with these powerful contracts. If you're comfortable with the basics of options and their terminology, then jump ahead to the next section.
Options are financial contracts between the buyer and seller to exchange if certain conditions are met. First, we have the strike price, which is the specified price at which the underlying asset can be bought (or sold). In general, the price of the underlying stock must reach the strike price for the option to be valuable (called in the money). If the option expires in the money, the owner can exercise that option in order to transact those shares at the agreed upon price. These contracts also have time limits, or expiration dates, so if they expire out of the money, then they expire worthless and the holder loses all of their initial investment (sounds a lot like our insurance contract, no?).
Even that was too technical sounding for my liking, so let's illustrate with an example.
Alice buys a call option 3 months out from Bob for $5 per contract on SPY at a strike price of $420. SPY is currently trading at $400. A call option gives Alice the right, but not the obligation, to buy SPY for the strike price, so she's only going to do that if it reaches $420 or more. Fast forward 3 months, and we see SPY is trading at $435, Alice exercises and just made $10 on the trade (options on stocks are usually traded in 100 contract lots, so she would have made $1,000 with a standard trade, but we can ignore that for now).
We plot Alice's payoff profile below.
Her returns increase dollar for dollar beyond the strike price, but she does have to overcome the $5 cost of the contract, think again if you think Bob is just going to give her the option of taking his SPY shares without getting anything in return!
Call options like this, pay the owner when the stock goes up. It's cousin is the put option which gives the owner the right, but not the obligation, to sell shares at a specified price.
Bob has a model that says the S&P 500 is ready for a correction, so he spends $4 to buy a 12-month put option from Alice, which gives him the right to sell SPY for $430 while it's currently trading at $445. His model was wrong and, one year later, the option expires worthless because SPY is trading above $430.
Here, Bob does better and better the lower the price sinks. If he owns a put in a major crash, he can make a killing very quickly.
This looks just like our safe haven. We pay a small premium to own the insurance and we get paid if our shares crater, recouping some of our losses.
Pricing Safe Haven Options
Now that we have a good understanding of the most basic math behind options, we can use this to figure out whether or not options on the market are cheap or expensive.
Our cost-effective boundary stipulates that we need at least a 680% return for insurance to make sense. If we pay $1 for an option, then we need the price of our put to move an additional $6.80 below the strike price for us to reach our cost-effective threshold.
To keep things simple, say SPY is trading at $100 per share. We're insuring against a 15% downward move, meaning we want protection if it hits $85 within the next year.
From here, if we wanted to calculate a target strike price, we could derive the following relationship:
Where K is our strike price, C is our option price, P_target is the target price we're protecting ourselves against (e.g. $85 SPY), and 𝜂 is our efficient return boundary (680%).
If C is $1, then we can plug these values in and get a strike price of $92.80 for our option contract. Any strike price above this value is just bonus for us and moves us closer to the ideal insurance model Spitznagel shows us.
The trouble is, we don't set C or K, these are given to us in the option chains, so we really have two values that we plug in and get from the market. So a better way to do this, would be to re-arrange the equation and solve for C, then we can plug in the market data for K and C, and any point above our P_target where C < K means we have a cost-effective hedge we can buy.
Let's walk through a brief computational example to show how this could be run.
First, we need to get some option data, which we can access using our trusty yfinance package.
import yfinance as yf from datetime import datetime, timedelta
We can get S&P 500 option chain and ETF data from yfinance's Ticker method. Once we instantiate that, we'll get a long list of option expiry dates. We're interested in getting options one year out, so we could manually pick the date from our list, but here we're all about automatic, algorithmic trading, so we want the computer to do that for us. We'll convert the string of dates to datetime objects, then find the minimum value between our list of dates and one year from now. The argmin() method gives us the index associate with the closest date.
ticker = yf.Ticker('SPY') option_idx = np.abs((pd.to_datetime(ticker.options) - (datetime.now() + timedelta(365))).days).argmin() option_date = ticker.options[option_idx] option_date
I'm writing this on March 19, 2022, so the date we picked out is almost exactly one year from today. So I'm satisfied.
Now that we have our expiration date, we can get a data frame for all of the puts that expire on that date for the SPY ETF.
opt = ticker.option_chain(option_date).puts opt.head()
The SPY options are very liquid and so you'll see that there are dozens of strikes available to choose from. What we're interested in is where our simple formula shows that the lastPrice is greater than our efficient price.
Here's a quick function that will let us calculate that.
def calcEfficientPrice(K: float, target_price: float, eff: float): ''' K: strike price target_price: target price per share eff: efficient boundary ''' return (K - target_price) / (1 + eff)
Now we're ready to put it all together and see if we can actually buy cost-effective insurance as retail investors!
target_price = ticker.info['regularMarketPrice'] * 0.85 eff = 6.8 opt['eff_price'] = opt.apply(lambda x: calcEfficientPrice( x['strike'], target_price, eff), axis=1) opt.loc[opt['lastPrice']<=opt['eff_price']]
And...we get nothing...
There are no options that are currently cheap enough for us to buy at this point to replicate the insurance type of payoff we're looking for!
What's a safe haven investor to do?
Thankfully, we aren't stuck. In the previous post, we saw how having some cash can boost our overall portfolio's returns if we follow a strategy like the Kelly Criterion. So we could allocate some of our portfolio to cash accordingly and wait for the market price for insurance to drop.
Another strategy to investigate would be to purchase options on the major components of the S&P 500 with an understanding that these few names make up most of the index and thus are highly correlated with its performance. If Amazon, Facebook, Google, and other mega caps start to tank, they'll probably take the whole index with it and it may be more efficient to buy insurance on these individual names than on the index as a whole.
The downside is that you're not getting the same kind of protection for your portfolio because you're relying on these names being highly correlated. For example, what happens if you buy puts on Apple, but it continues to do well while the S&P 500 drops? In this case, you're not getting any protection! The other drawback here is that you're going to need more cash if you buy puts in multiple mega cap stocks than you would if you just bought a single option contract. So this approach might not be possible if your account is too small.
Ok, what about an inverse ETF?
There are a host of these ETFs available, some with leverage to give you even more returns in a meltdown. There are costs to these and you'd have to look into the performance to see if they'd be efficient. I remain skeptical if they'd actually give the payoff profile we're looking for because a 3x inverse ETF would expect a 45% return on a 15% drop in the S&P 500, which is significantly less than the leverage an option gives us.
Overlaying an indicator or some predictive model on when to insure and when to hold cash would help us decrease that efficient boundary price and potentially allow us to insure when the odds of a crash are higher. It makes our insurance more tactical rather than maintaining a constant allocation. Of course, when we see a correction coming, the market likely will too, which would raise the price of our options (although many traders look at the put/call ratio as a contrarian indicator to spot tops). This tactical allocation goes against Spitznagel's idea of not predicting crashes or corrections too, but could help improve the precision of our safe haven allocations giving us that boost to our portfolio when we need it the most.
Is a true safe haven investment a mythical construct?
Spitznagel swears he's found them, but doesn't disclose any in his book. Certainly our one look at current options contracts don't falsify the idea, but the gap between our efficient price and the market prices do make me wonder.
While there are no magic bullets in investing, the ideas and concepts raised are incredibly valuable. Managing our risk shouldn't be seen as a cost (if done right) and is what separates those who blow up their accounts from successful traders.
We're working hard to put tested, quantitative tools into your hands so that you can control your risk without losing the upside on your trading strategies.
Get started today by checking out a free demo of our no-code algorithmic trading platform!