It's somewhat hard to find data about historical prices of options, so I will proceed differently as promissed. I will work on the following problem:
I have a portfolio of 1000 GOOG stocks on Jannuary 1 2005. I want to hedge the portfolio whenever the running volatility is bigger than 40%, otherwise I will let it growth.
Not having historical prices on options I will use the theoretical Black-Scholes-Merton (BSM) price for options. This is problematic in some ways. First, the volatility I'm using at some time may not be the implied volatility used at that time for the real options sold and this means that I'm ignoring volatility smiles. Then there is the fact I need the free-risk interest rates and I don't have them, so I will build a model for it just to account for the changes after the financial crisis of the 2008.
In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
In [2]:
data = pd.read_csv("../data/GOOG.csv").ix[:,["Date", "Open"]]
data.sort_values(by="Date", inplace=True)
data.reset_index(inplace=True)
For the interest rate to be used, observing the charts of LIBOR rates, I will pick a linear model, starting from 2.5% at Jan-1-2005, going to 6% at Dec-31-2007 and decaying to 0.5% until Dec-31-2007 and staying there.
In [3]:
rate = np.zeros_like(data.Date)
n = 0
m = 0
for d in data.Date:
if d < "2007-12-31":
rate[m] = 2.5 + n*3.5/734.0
if d == "2007-12-31":
rate[m] = 6.0
n = 0
if "2008-12-31" > d > "2007-12-31":
rate[m] = 6.0 - 5.5*n/256
if d >= "2008-12-31":
rate[m] = 0.5
m +=1
n +=1
rate = rate/100
That's all. Now I will measure volatility on the last running two weeks and decide if I will hedge that risk. Else return to the initial position. Options are bought or sold at the Black-Scholes price using the current rate and the running volatility. This mean that the first month I will keep the original portfolio, until I have enough (?) info about the market.
I will calculate the returns beforehand, so that I do not need to deal with pandas Series. It must be pointed that on prices there are a split on the stocks at April 2 2014, so I need to repair this split. Also, I will define the formulas for the Delta (of the BSM model), the option pricing, the annual volatility.
On this simulation I will use only put options bought at the suggested price by the BSM model, as I assume that high volatility can lead to losses and want to hedge that high volatility times. I will not worry if volatility is low and expect that the stock make gains.
In [4]:
from scipy.stats import norm
def volatility(v):
return np.sqrt(260)*np.std(v)
def eu_put_option(S, K, r, s, t):
d1 = (np.log(S/K) + t*(r + s*s/2))/np.sqrt(s*s*t)
d2 = (np.log(S/K) + t*(r - s*s/2))/np.sqrt(s*s*t)
return K*np.exp(-r*t)*norm.cdf(-d2) - S*norm.cdf(-d1)
def eu_put_option_delta(S, K, r, s, t):
return norm.cdf((np.log(S/K) + t*(r + s*s/2))/np.sqrt(s*s*t)) - 1
def repair_split(df, info): # info is a list of tuples [(date, split-ratio)]
temp = df.Open.values.copy()
for i in info:
date, ratio = i
mask = np.array(df.Date >= date)
temp[mask] = temp[mask]*ratio
return temp
stock_price = repair_split(data, [("2014-03-27", 2)])
rets = np.diff(np.log(stock_price))
I will define two functions, one to open a hedge position, selling if necessary some stock in order to affor the position. The other function will close the position, buying all possible stock with the earnings, if any. A transaction cost is included. It must account of both opening and closing the position (so, if your brokers charges you \$50, the commission is \$100)
In [5]:
commission = 0
def rebalance(S, K, r, s, t, cap):
option_price = eu_put_option(S, K, r, s, t)
delta = eu_put_option_delta(S, K, r, s, t)
# rebalance the portfolio, if not enough money, then sell stock to buy put options
options = np.floor(cap/(option_price - delta*S))
stock = np.floor(-delta*cap/(option_price - delta*S))
money = cap - (options*option_price + stock*S)
return (stock, options, money, option_price)
def close_position(S, K, nstock, nopt, mon):
profit = nopt*max(0, K - S)
money = mon + profit
stock = nstock + np.floor(money/S)
money -= (stock - nstock)*S
money -= commission
return (profit, stock, 0, money)
Now I will proceed to explain how this trading algorithm will work:
The algorithm will begin with $200000, that are enough to buy 1000 (actualy a little more) stocks of GOOG (at that time). I will count the balance only as the current money in hand and the price of stocks. The price of options don't count here as part of the portfolio price. This is because the money spent on buying options is lost, and will not return as they arent American and I don't intend to transfer them.
The simulation will begin with 60 days of heating to get enough data to calculate 15-days volatility.
In [6]:
capital = 200000 # just enough to buy the stocks
stock = 1000
money = capital - 1000*stock_price[0] # this money will not take any interest
options = 0
strike = 0
option_price = 0
profit = 0
net_worth = []
vola = []
n_options = []
n_stock = []
n = 0
sell_options = 0
print("Not invested money: {0}".format(money))
for d in data.Date:
capital = money + stock*stock_price[n]
net_worth.append(capital)
if n<60:
n += 1
continue
# here begins the simulation
vol = volatility(rets[n-15:n])
vola.append(vol)
if sell_options == 0 and options > 0:
(profit, stock, options, money) = close_position(stock_price[n], strike, stock, options, money)
print("\nSell options: {0}".format(data.Date[n]))
print(" Profit: {0}".format(profit))
print(" Stock price at {0}, strike at {1}".format(stock_price[n], strike))
print(" Current balance: {0}".format(money + stock*stock_price[n]))
if vol > 0.5 and options == 0:
strike = stock_price[n] + 20
(stock, options, money, option_price) = rebalance(stock_price[n], strike, rate[n], vol, 30/260.0, capital);
print("\nBuy options: {0}".format(data.Date[n]))
print(" Put option price (stock price at {0}, strike at {1}): {2}".format(stock_price[n], strike, option_price))
print(" Position: {0} stock, {1} options, money: {2}".format(stock, options, money))
print(" Current balance: {0}".format(money + stock*stock_price[n]))
sell_options = 90
if sell_options > 0:
sell_options -= 1
n_options.append(options)
n_stock.append(stock)
n += 1
In [7]:
plt.figure(figsize=(9,9))
plt.subplot(311)
plt.plot(1000*stock_price, label="GOOG")
plt.plot(net_worth, label="Portfolio")
plt.legend(loc=0)
plt.xlim(0,len(net_worth))
plt.subplot(312)
plt.plot(n_stock, label="Stock")
plt.legend(loc=0)
plt.subplot(313)
plt.plot(n_options, label="Put options")
plt.legend(loc=0)
plt.tight_layout()
Well, this is nice and all. The algorithm outperformed Google. But as you can expect, the algorithm has a few shortcommings:
But it's still a nice model to show how an automated hedging system can manage during risky times, at least in theory. About cherrypicking, this is a form of bias, the market will not necessarily work the same as it did in the past, so backtesting is important, as is important to not fit on the test data (the same as any machine learning problem).
In [ ]: