项目作者: bottama

项目描述 :
Algorithmic Portfolio Hedging. Black-Scholes Pricing for Dynamic Hedges to produce a Dynamic multi-asset Portfolio Hedging with the usage of Options contracts.
高级语言: Python
项目地址: git://github.com/bottama/Dynamic-Derivatives-Portfolio-Hedging.git


Dynamic Portfolio Hedging

Delta, Gamma and Vega Portfolio Neutral

Last Update March 12, 2021

Matteo Bottacini, matteo.bottacini@usi.ch

Project description

In this project it is discussed how to construct a Dynamic multi-asset Portfolio Hedging with the usage of Options contracts.

NVDA boomed over the last 2 years and here is discussed how to hedge a short position in NVDA calls.
The aim is to hedge the exposure to changes in volatility, movements in the underlying asset and the speed of movements in the underlying asset.

Options have exposure to not only the underlying asset but also interest rates, time, and volatility.
These exposures are inputs to the Black-Scholes option pricing model.

While building the script, it is also explored the intuition behind the Black-Scholes model.

Folder structure:

  1. Dynamic-Derivatives-Portfolio-Hedging/
  2. deliverables/
  3. asset-allocation.py
  4. src/
  5. utils.py
  6. variables.py
  7. README.md

Content

  • Main variables
  • Black-Scholes-Merton (BS) model
  • Greeks
  • Dynamic hedging
  • Portfolio position
  • Greek neutralization
  • Final Delta, Gamma, and Vega Neutral Portfolio
  • Conclusion

Main variables

In the file ../src/variables.py are set all the different variables that for a dynamic hedge needs to be updated daily.
Feel free to play with these variables and create different settings.

  1. # main variables
  2. # asset_price : underlying price
  3. # sigma : implied volatility to insert for the BS model
  4. # dt : time to expiration
  5. # rf : risk free rate (30 day LIBOR rate)
  6. # nContract1 : number of contracts
  7. # K1 : strike price option 1
  8. # K2 : strike price option 2
  9. # K3 : strike price option 3
  10. # underlying
  11. asset_price = 543
  12. # input BS
  13. sigma = 0.53
  14. dt = 30/365
  15. rf = .015
  16. # option 1
  17. nContract1 = -1000
  18. K1 = 545
  19. # option 2
  20. K2 = 550
  21. # option 3
  22. K3 = 570

Black-Scholes-Merton (BS) model

In the file ../src/utils.py is described the model to price the European Options with the BS model.

The following code models European calls:

  1. import math
  2. from scipy.stats import norm
  3. # define the European call option
  4. class EuropeanCall:
  5. def call_price(
  6. self, asset_price, asset_volatility, strike_price,
  7. time_to_expiration, risk_free_rate
  8. ):
  9. b = math.exp(-risk_free_rate * time_to_expiration)
  10. x1 = math.log(asset_price / (b * strike_price)) + .5 * (
  11. asset_volatility * asset_volatility) * time_to_expiration
  12. x1 = x1 / (asset_volatility * (time_to_expiration ** .5))
  13. z1 = norm.cdf(x1)
  14. z1 = z1 * asset_price
  15. x2 = math.log(asset_price / (b * strike_price)) - .5 * (
  16. asset_volatility * asset_volatility) * time_to_expiration
  17. x2 = x2 / (asset_volatility * (time_to_expiration ** .5))
  18. z2 = norm.cdf(x2)
  19. z2 = b * strike_price * z2
  20. return z1 - z2
  21. def __init__(
  22. self, asset_price, asset_volatility, strike_price,
  23. time_to_expiration, risk_free_rate
  24. ):
  25. self.asset_price = asset_price
  26. self.asset_volatility = asset_volatility
  27. self.strike_price = strike_price
  28. self.time_to_expiration = time_to_expiration
  29. self.risk_free_rate = risk_free_rate
  30. self.price = self.call_price(asset_price, asset_volatility, strike_price, time_to_expiration, risk_free_rate)

The following code models European puts:

  1. import math
  2. from scipy.stats import norm
  3. # define the European put option
  4. class EuropeanPut:
  5. def put_price(
  6. self, asset_price, asset_volatility, strike_price,
  7. time_to_expiration, risk_free_rate
  8. ):
  9. b = math.exp(-risk_free_rate * time_to_expiration)
  10. x1 = math.log((b * strike_price) / asset_price) + .5 * (
  11. asset_volatility * asset_volatility) * time_to_expiration
  12. x1 = x1 / (asset_volatility * (time_to_expiration ** .5))
  13. z1 = norm.cdf(x1)
  14. z1 = b * strike_price * z1
  15. x2 = math.log((b * strike_price) / asset_price) - .5 * (
  16. asset_volatility * asset_volatility) * time_to_expiration
  17. x2 = x2 / (asset_volatility * (time_to_expiration ** .5))
  18. z2 = norm.cdf(x2)
  19. z2 = asset_price * z2
  20. return z1 - z2
  21. def __init__(
  22. self, asset_price, asset_volatility, strike_price,
  23. time_to_expiration, risk_free_rate
  24. ):
  25. self.asset_price = asset_price
  26. self.asset_volatility = asset_volatility
  27. self.strike_price = strike_price
  28. self.time_to_expiration = time_to_expiration
  29. self.risk_free_rate = risk_free_rate
  30. self.price = self.put_price(asset_price, asset_volatility, strike_price, time_to_expiration, risk_free_rate)

Greeks

Using a Taylor series expansion we can derive all the greeks.
The greeks tell us how we can expect an option or portfolio of options to change when a change occurs in one or more of the option exposures.
Something important to note is that all first-order approximations are linear, and the option pricing function is non-linear.
This means the more the underlying parameter deviates from the initial partial-derivative calculation the less accurate it will be.

Delta

Delta is the first-order-partial derivative with respect to the underlying asset of the BS model.
Delta refers to how the option value changes when there is a change in the underlying asset price.
The following code is part of the ../src/utils.py:

  1. # Call delta
  2. def call_delta(
  3. self, asset_price, asset_volatility, strike_price,
  4. time_to_expiration, risk_free_rate
  5. ):
  6. b = math.exp(-risk_free_rate * time_to_expiration)
  7. x1 = math.log(asset_price / (b * strike_price)) + .5 * (
  8. asset_volatility * asset_volatility) * time_to_expiration
  9. x1 = x1 / (asset_volatility * (time_to_expiration ** .5))
  10. z1 = norm.cdf(x1)
  11. return z1
  12. # Put delta
  13. def put_delta(
  14. self, asset_price, asset_volatility, strike_price,
  15. time_to_expiration, risk_free_rate
  16. ):
  17. b = math.exp(-risk_free_rate * time_to_expiration)
  18. x1 = math.log(asset_price / (b * strike_price)) + .5 * (
  19. asset_volatility * asset_volatility) * time_to_expiration
  20. x1 = x1 / (asset_volatility * (time_to_expiration ** .5))
  21. z1 = norm.cdf(x1)
  22. return z1 - 1

Gamma

Gamma is the second-order-partial-derivative with respect to the underlying of the BS model.
Gamma refers to how the option’s delta changes when there is a change in the underlying asset price.
The following code is part of the ../src/utils.py:

  1. # Call gamma
  2. def call_gamma(
  3. self, asset_price, asset_volatility, strike_price,
  4. time_to_expiration, risk_free_rate
  5. ):
  6. b = math.exp(-risk_free_rate * time_to_expiration)
  7. x1 = math.log(asset_price / (b * strike_price)) + .5 * (
  8. asset_volatility * asset_volatility) * time_to_expiration
  9. x1 = x1 / (asset_volatility * (time_to_expiration ** .5))
  10. z1 = norm.cdf(x1)
  11. z2 = z1 / (asset_price * asset_volatility * math.sqrt(time_to_expiration))
  12. return z2
  13. # Put gamma
  14. def put_gamma(
  15. self, asset_price, asset_volatility, strike_price,
  16. time_to_expiration, risk_free_rate
  17. ):
  18. b = math.exp(-risk_free_rate * time_to_expiration)
  19. x1 = math.log(asset_price / (b * strike_price)) + .5 * (
  20. asset_volatility * asset_volatility) * time_to_expiration
  21. x1 = x1 / (asset_volatility * (time_to_expiration ** .5))
  22. z1 = norm.cdf(x1)
  23. z2 = z1 / (asset_price * asset_volatility * math.sqrt(time_to_expiration))
  24. return z2

Vega

Vega is the first-order-partial-derivative with respect to the underlying asset volatility of the BS model.
Vega refers to how the option value changes when there is a change in the underlying asset volatility.
The following code is part of the ../src/utils.py:

  1. # Call vega
  2. def call_vega(
  3. self, asset_price, asset_volatility, strike_price,
  4. time_to_expiration, risk_free_rate
  5. ):
  6. b = math.exp(-risk_free_rate * time_to_expiration)
  7. x1 = math.log(asset_price / (b * strike_price)) + .5 * (
  8. asset_volatility * asset_volatility) * time_to_expiration
  9. x1 = x1 / (asset_volatility * (time_to_expiration ** .5))
  10. z1 = norm.cdf(x1)
  11. z2 = asset_price * z1 * math.sqrt(time_to_expiration)
  12. return z2 / 100
  13. # Put vega
  14. def put_vega(
  15. self, asset_price, asset_volatility, strike_price,
  16. time_to_expiration, risk_free_rate
  17. ):
  18. b = math.exp(-risk_free_rate * time_to_expiration)
  19. x1 = math.log(asset_price / (b * strike_price)) + .5 * (
  20. asset_volatility * asset_volatility) * time_to_expiration
  21. x1 = x1 / (asset_volatility * (time_to_expiration ** .5))
  22. z1 = norm.cdf(x1)
  23. z2 = asset_price * z1 * math.sqrt(time_to_expiration)
  24. return z2 / 100

Theta

Theta is the first-oder-partial-derivative with respect to the time until option expiration of the BS model.
Theta refers to how the option value changes as time passes.

Rho

Rho is the first-order-partial-derivative with respect to the risk-free rate of the BS model.
Rho refers to how the option value changes as the interest rate changes.

Dynamic Hedging

The first thing to realize is that to neutralize exposure to greeks we are going to need offsetting positions in other options.
There are three greeks to neutralize, so we need three options to create three equations of greeks and weights with three unknowns (the weights in the other tradable options).

However, the trick here is realizing that the partial derivative of the underlying asset with respect to itself is just 1, this means the underlying asset has a delta of 1 and all other greek values are 0.
This means we can construct a portfolio of two tradable options, find appropriate weights to neutralize the greeks, then take an offsetting position in the underlying asset — effectively neutralizing exposure to all three greeks.

Portfolio position

To neutralize the portfolio it’s important at first understanding the overall position.
running the code in ../deliverables/asset_allocation.py it is first shown the initial portfolio position

  1. from src.utils import *
  2. from src.variables import *
  3. # Portfolio position
  4. option1 = EuropeanCall(asset_price=asset_price,
  5. asset_volatility=sigma,
  6. strike_price=K1,
  7. time_to_expiration=dt,
  8. risk_free_rate=rf)
  9. # theoretical value of the position
  10. print('Theoretical Initial Portfolio value: ', str(option1.price * abs(nContract1)))
  11. # greeks
  12. print('Initial Portfolio Greeks:\n '
  13. 'Delta: {}\n '
  14. 'Gamma: {}\n '
  15. 'Vega: {}'.format(option1.delta * nContract1,
  16. option1.gamma * nContract1,
  17. option1.vega * nContract1))

and then the output will be:

  1. >>> Theoretical Initial Portfolio value: 32264.05329034736
  2. >>> Initial Portfolio Greeks:
  3. Delta: -523.8788365375873
  4. Gamma: -6.3495209433350475
  5. Vega: -815.5392717775394

Greek neutralization

The greeks we are interested in neutralizing in the current portfolio can be expressed as a vector:

equation

The goal is to find the weights of the three assets we are capable of trading to neutralize these values.
First, we will look to neutralize gamma and vega, then using the underlying asset, we will neutralize delta.

equation

This means by inverting the matrix containing the greek values for the tradable options we can find the appropriate weights.
equation

this is the code at ../deliverables/asset-allocation.py:

  1. from src.utils import *
  2. from src.variables import *
  3. import numpy as np
  4. # Price option2 and option3 and find the greeks
  5. option2 = EuropeanCall(asset_price=asset_price,
  6. asset_volatility=sigma,
  7. strike_price=K2,
  8. time_to_expiration=dt,
  9. risk_free_rate=rf)
  10. option3 = EuropeanCall(asset_price=asset_price,
  11. asset_volatility=sigma,
  12. strike_price=K3,
  13. time_to_expiration=dt,
  14. risk_free_rate=rf)
  15. # greek neutralization -- gamma and vega
  16. greeks = np.array([[option2.gamma, option3.gamma], [option2.vega, option3.vega]])
  17. portfolio_greeks = [[option1.gamma * abs(nContract1)], [option1.vega * abs(nContract1)]]
  18. inv = np.linalg.inv(np.round(greeks, 2)) # We need to round otherwise we can end up with a non-invertible matrix
  19. # position on option 2 and 3 to be gamma and vega neutral
  20. w = np.dot(inv, portfolio_greeks)

Now that the exposure to gamma and vega is neutralized we need to neutralize our new exposure to delta.
To find our new exposure, we take the sum-product of all option positions in our portfolio with their respective deltas.

this is the code at ../deliverables/asset-allocation.py:

  1. # Greeks including delta
  2. portfolio_greeks = [[option1.delta * nContract1], [option1.gamma * nContract1], [option1.vega * nContract1]]
  3. greeks = np.array([[option2.delta, option3.delta], [option2.gamma, option3.gamma], [option2.vega, option3.vega]])
  4. w_stock = (np.round(np.dot(np.round(greeks, 2), w) + portfolio_greeks))[0]

Final Delta, Gamma, and Vega Neutral Portfolio

the final positions are the following:

  1. >>> Final asset allocation:
  2. option1: -1000
  3. option2: 8641
  4. option3: -8006
  5. underlying asset: -46

Conclusion

In this project, I’ve learned how to build Delta, Gamma and Vega neutral Portfolio to hedge the exposure against changes in volatility, movements in the underlying asset and the speed of movements in the underlying asset.
The code can be implemented directly into a live trading system in order to actively hedge the portfolio position day by day.
Hedging all Greek letters may require option positions greater than the original position.
Because of the limitations (instantaneous, local measures, model risk) of Greek letter hedging, this may rather increase than decrease risk.

The material discussed in this project it’s not financial advice.

Supported versions

This configuration has been tested against Python 3.8