Dynamic hedge

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:

  • Buy all possible stock if the running volatility is low (last 30 working days)
  • If volatility is bigger than 50%, buy put european options to hedge possible losses
    • Options are bought at BSM price with the current volatility and risk-free rate
    • If not enough money to afford the position, rebalance by selling some stock
    • Execution time will be 90 "working days" to simplify the code
  • On execution, if options are on the money, make some action on the same step:
    • Sell stock at strike price
    • get money
    • buy again stock at market price
    • this way I get only the difference as money without touching my stocks
  • If volatility is still high don't rebalance options until they expire
  • If volatility is still high on expiration, buy options and rebalance again

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


Not invested money: 5619.659000000014

Buy options: 2005-04-28
    Put option price (stock price at 219.50037200000003, strike at 239.50037200000003): 27.436677403339218
    Position: 860.0 stock, 1320.0 options, money: 133.29690759227378
    Current balance: 188903.61682759228

Sell options: 2005-09-06
    Profit: 0.0
    Stock price at 289.0005, strike at 239.50037200000003
    Current balance: 248673.72690759227

Buy options: 2006-01-24
    Put option price (stock price at 436.03075599999994, strike at 456.03075599999994): 41.880522192349645
    Position: 733.0 stock, 1324.0 options, money: 59.39153692132095
    Current balance: 319669.9356849213

Sell options: 2006-06-02
    Profit: 91607.67783599993
    Stock price at 386.840667, strike at 456.03075599999994
    Current balance: 375221.27828392125

Buy options: 2007-11-15
    Put option price (stock price at 638.571099, strike at 658.571099): 53.88519723460536
    Position: 833.0 stock, 1607.0 options, money: 624.8294689103495
    Current balance: 532554.5549359104

Sell options: 2008-03-28
    Profit: 339254.3195939999
    Stock price at 447.46075700000006, strike at 658.571099
    Current balance: 712613.9596439103

Buy options: 2008-04-18
    Put option price (stock price at 535.2108900000001, strike at 555.2108900000001): 63.61647876104263
    Position: 1286.0 stock, 2574.0 options, money: 282.1505089865532
    Current balance: 688563.3550489866

Sell options: 2008-08-26
    Profit: 184684.67760600013
    Stock price at 483.460821, strike at 555.2108900000001
    Current balance: 806697.4439209866

Buy options: 2008-09-19
    Put option price (stock price at 461.000782, strike at 481.000782): 48.22558825489736
    Position: 1397.0 stock, 2596.0 options, money: 22.379305273178034
    Current balance: 644040.4717592732

Sell options: 2009-01-29
    Profit: 354250.69477600005
    Stock price at 344.540576, strike at 481.000782
    Current balance: 835596.2587532732

Buy options: 2009-02-23
    Put option price (stock price at 347.00060299999996, strike at 367.00060299999996): 35.81989261612628
    Position: 2065.0 stock, 3488.0 options, money: 65.79358822491486
    Current balance: 716622.0387832248

Sell options: 2009-07-01
    Profit: 0.0
    Stock price at 424.200732, strike at 367.00060299999996
    Current balance: 876040.305168225

Buy options: 2014-04-11
    Put option price (stock price at 1065.104762, strike at 1085.104762): 88.07350113106668
    Position: 1773.0 stock, 3525.0 options, money: 617.2926052147523
    Current balance: 1889048.0356312147

Sell options: 2014-08-20
    Profit: 0.0
    Stock price at 1171.76532, strike at 1085.104762
    Current balance: 2078157.2049652147

Buy options: 2015-07-17
    Put option price (stock price at 1298.0, strike at 1318.0): 111.57453473562703
    Position: 1509.0 stock, 3073.0 options, money: 420.7473626327701
    Current balance: 1959102.7473626328

Sell options: 2015-11-23
    Profit: 0.0
    Stock price at 1514.900024, strike at 1318.0
    Current balance: 2286404.883578633

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:

  • You need someone selling put options during a recession
  • If you watch the logs, I buy put options with non integer price
  • It depends on high volatility to make money
  • I'm ignoring market liquidity
  • BSM model shortcomings
  • I cherrypicked the values, this kind of algorithms must be backtested

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 [ ]: