## 由最近市场波动引发的关于对冲(Hedging)的几点思考

### 有自己view的对冲

1. 假如我现在long 10K USD SPY spot position，我的view是2018年会比较波动但不会有大危机，预期5%-10%的回报率。这种情况下，我就可以做一个collar，short out-of-money call 同时 long out-of-money put，这样的hedge不会很贵因为premium抵消一些，可以做到获取一定范围内的upsides收益，同时也有downside protection.
2. 假如我的view是美股今年可能继续大涨，downside risk不会很大。那我可以在现有Long spot position基础上，Long higher strike OTM put, 同时short lower strike OTM put. 这样我们去除了一部分downside risk，同时hedge不会很贵。唯一的风险是美股大幅下跌的情况，但根据我们的view是小概率事件。
3. 假如我现在Long FB股票，但市场对科技股情绪下降，如果我相信FB的前景和基本面好于其他科技股，如Twitter，特斯拉等，我可以选择short 这些科技股来对冲掉科技股行业的风险，只留下company specific risk。这就是Long short的思想。或者我可以short tech index，因为我相信FB会outperform 行业。

## European Vanilla Option Pricing – Monte Carlo Methods

1. We start with the assumption that underlying follow Geometric Brownian Motion (GBM): 1. We use Ito’s Lemma with , then we have   By Ito’s Lemma, we have 1. Therefore, the change of between time 0 and future time T, is normally distributed as following: Thus, the future underlying price can be written as,  is the noise term from standard normal distribution.

Note, we will take , which is the risk free rate. This means investors are risk neutral and requires risk free return on underlying asset. This is to be consistent with the risk neutral probabilities used in simulation.
Correspondingly, we also use risk free rate in the discount factor in step 5.

1. So now we can simulate the future underlying price at expiry. With European Call or Put boundary condition to calculate the payoff.  1. We then need to discount the future payoff back to present by multiplying a discount factor, 1. The above two steps are repeated many times and its expectation is calculated as the final simulation result.
python code：
def getMCPrice(self):
"""
Determine the option price using a Monte Carlo approach.
The log return of underlying follow Normal distribution.
s_T = s_t * exp((r - 1/2 * sig^2) * (T-t) + sig * sqrt(T-t) * sig_Normal)
"""
calc = np.zeros([self.iterations, 2])
rand = np.random.normal(0, 1, [1, self.iterations])
mult = self.spot * np.exp(self.tenor * (self.rate - 0.5 * self.sigma**2))

if self.callPut == 'Call':
calc[:,1] = mult * np.exp(np.sqrt((self.sigma**2)*self.tenor) * rand) - self.strike
elif self.callPut == 'Put':
calc[:,1] = self.strike - mult*np.exp(np.sqrt((self.sigma**2) * self.tenor) * rand)

avgPayOff = np.sum(np.amax(calc, axis=1)) / float(self.iterations)

return np.exp(-self.rate * self.tenor) * avgPayOff

def getBSPrice(self):
""" Determine the option price using the exact Black-Scholes expression. """
return blackScholesOptionPrice(self.callPut, self.spot, self.strike, self.tenor, self.rate, self.sigma)
We can run the above in Python console：
from option_pricer import EuropeanVanillaPricer
pricer = EuropeanVanillaPricer()
pricer.getMCPrice()
2.1620364099067015
pricer.getBSPrice()
2.1736062697012564
As we can see in the above Monte Carlo simulation, we rely on drawing random numbers, from a Standard Normal distribution.
Alternatively, we can use random numbers from a Uniform distribution, i.e. equal probability of each random number.

To do this, we combine step 3, 4 and 5, the current option price is obtained by integrating the terminal payoff under the risk neutral measure:    In the first line, function is just the payoff condition at expiry. As we are integrating with regard to , which follows Standard Normal distribution, the last term is the probability density function.
In the second line, we just use a new function h of epsilon to make the expression more compact.
In the third line, we do inverse transformation to integrate with regard to the cumulative probability, .

So now it becomes an integral of function over the UNIFORM distribution with range [0, 1].
Now our simulation task becomes taking random number from the Uniform distribution [0, 1], and then calculate integral of function using Monte Carlo.

To be more specific, our task has been changed from calculating To evaluating When we use Monte Carlo to estimate function integral, we may run into problem of random number clustering, which essentially leads to Convergence rate of .
To conquer this issue, instead of using pseudo-random numbers, we can use a deterministic sequence, whose numbers are more equally spaced. And this is exactly what Quasi Monte Carlo (QMC) does. More details on the low-discrepancy sequences can be found in this post.

## 用Python计算分析实现波动率和隐含波动率

Python代码可以详见:
class VolatilityPricer():
"""
Realized vol:
Same as Black-Scholes, we assume the underlying follows a Geometric Brownian Motion.
Then its log return follows a Normal distribution, with mean as 0.
We take as input the historical daily underlying prices.
Annualization factor is 252.
Degree of Freedom is 0 as we are calculating the exact realized vol for the given historical period.

Implied vol:
Use Black-Scholes to back out the implied volatility from the given market option price.

"""

def __init__(self):
self.historicalDataBySymbol = dict()
self.dataHub = DataHub()
self.realizedVolBySymbol = dict()

def _loadHistoricalUnderlyingData(self, startDate, endDate, symbols):

def _calculateRealizedVol(self, ts):
""" Calculate the realized vol from given time series """
pctChange = ts.pct_change().dropna()
logReturns = np.log(1+pctChange)
vol = np.sqrt(np.sum(np.square(logReturns)) / logReturns.size)
annualizedVol = vol * np.sqrt(252)

return annualizedVol

def getRealizedVol(self, startDate=datetime.date.today()-datetime.timedelta(days=30), endDate=datetime.date.today(), symbols=['SPY']):
""" Calculate the realized volatility from historical market data """

for symbol, df in self.historicalDataBySymbol.iteritems():
# Use daily Close to calculate realized vols
realizedVol = self._calculateRealizedVol(df.loc[:, 'Close'])
self.realizedVolBySymbol[symbol] = realizedVol

return self.realizedVolBySymbol

def getImpliedVol(self, optionPrice=17.5, callPut='Call', spot=586.08, strike=585.0, tenor=0.109589, rate=0.0002):
""" Calculate the implied volatility from option market price """
return blackScholesSolveImpliedVol(optionPrice, callPut, spot, strike, tenor, rate)

### 计算股票的实现波动率

from volatility_pricer import VolatilityPricer
vp = VolatilityPricer()
vp.getRealizedVol()
{'SPY': 0.086197389793546381}
vp.getRealizedVol(startDate=datetime.date(2018,1,1))
{'SPY': 0.16562165494524139}

### 计算给定期权的隐含波动率

from volatility_pricer import VolatilityPricer
vp = VolatilityPricer()
vp.getImpliedVol()
0.21921387741959775