aboutsummaryrefslogtreecommitdiffstats
path: root/python/analytics/option.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/analytics/option.py')
-rw-r--r--python/analytics/option.py50
1 files changed, 23 insertions, 27 deletions
diff --git a/python/analytics/option.py b/python/analytics/option.py
index bd1803b0..065de803 100644
--- a/python/analytics/option.py
+++ b/python/analytics/option.py
@@ -11,10 +11,10 @@ from .utils import GHquad, build_table
from .index import g, ForwardIndex, CreditIndex
from .db import _engine
from .utils import memoize
-from yieldcurve import roll_yc
from pandas.tseries.offsets import BDay
-from pyisda.flat_hazard import pv_vec
+from pyisda.optim import init_context, update_context, expected_pv
+import pyisda.optim
from scipy.optimize import brentq
from scipy.integrate import simps
from scipy.interpolate import SmoothBivariateSpline, interp1d, CubicSpline
@@ -25,16 +25,13 @@ from multiprocessing import Pool
from functools import partial, lru_cache
from itertools import chain
from scipy.optimize import least_squares
+from scipy import LowLevelCallable
+from scipy.integrate import quad
from scipy.special import logit, expit
-def calib(S0, fp, exercise_date, exercise_date_settle,
- index, tilt, w):
- S = S0 * tilt * 1e-4
- pv = pv_vec(S, index._yc, exercise_date, exercise_date_settle,
- index.start_date, index.end_date, index.recovery,
- index.fixed_rate * 1e-4)
- return np.inner(pv, w) - fp
+def calib(S0, fp, tilt, w, ctx):
+ return expected_pv(tilt, w, S0, ctx) - fp
def ATMstrike(index, exercise_date):
"""computes the at-the-money strike.
@@ -390,8 +387,8 @@ class Swaption(BlackSwaption):
def __init__(self, index, exercise_date, strike, option_type="payer",
direction="Long"):
super().__init__(index, exercise_date, strike, option_type, direction)
- self._Z, self._w = GHquad(50)
self._cache = {}
+ self._Z, self._w = GHquad(30)
def __hash__(self):
return super().__hash__()
@@ -400,9 +397,14 @@ class Swaption(BlackSwaption):
@memoize
def pv(self):
T = self.T
- tilt = np.exp(-self.sigma**2/2 * T + self.sigma * self._Z * math.sqrt(T))
- args = (self.forward_pv, self.exercise_date, self.exercise_date_settle,
- self.index, tilt, self._w)
+ if T == 0.:
+ return self.notional * self.intrinsic_value
+ sigmaT = self.sigma * math.sqrt(T)
+ tilt = np.exp(-0.5 * sigmaT**2 + sigmaT * self._Z)
+ ctx = init_context(self.index._yc, self.exercise_date, self.exercise_date_settle,
+ self.index.start_date, self.index.end_date, self.index.recovery,
+ self.index.fixed_rate * 1e-4, self._G, sigmaT, 0.01)
+ args = (self.forward_pv, tilt, self._w, ctx)
eta = 1.05
a = self.index.spread * 0.99
b = a * eta
@@ -412,29 +414,23 @@ class Swaption(BlackSwaption):
b *= eta
S0 = brentq(calib, a, b, args)
- if T == 0:
- return self.notional * self.intrinsic_value
+ update_context(ctx, S0)
+ my_pv = LowLevelCallable.from_cython(pyisda.optim, "pv", ctx)
## Zstar solves S_0 exp(-\sigma^2/2 * T + sigma * Z^\star\sqrt{T}) = strike
- Zstar = (math.log(self._strike/S0) + self.sigma**2/2 * T) / \
- (self.sigma * math.sqrt(T))
+ Zstar = (math.log(self._strike / S0) + 0.5 * sigmaT**2) / sigmaT
if self.option_type == "payer":
- Z = Zstar + np.logspace(0, math.log(4 / (self.sigma * math.sqrt(T)), 10), 300) - 1
+ val, err = quad(my_pv, Zstar, 12)
elif self.option_type == "receiver":
- Z = Zstar - np.logspace(0, math.log(4 / (self.sigma * math.sqrt(T)), 10), 300) + 1
- else:
- raise ValueError("option_type needs to be either 'payer' or 'receiver'")
- S = S0 * np.exp(-self.sigma**2/2 * T + self.sigma * Z * math.sqrt(T))
- r = pv_vec(S * 1e-4, self.index._yc, self.exercise_date,
- self.exercise_date_settle, self.index.start_date,
- self.index.end_date, self.index.recovery, self.index.fixed_rate * 1e-4)
- val = (r - self._G) * 1/math.sqrt(2*math.pi) * np.exp(-Z**2/2)
- return self._direction * self.notional * simps(val, Z) * self.df
+ val, err = quad(my_pv, Zstar, -12)
+ return self._direction * self.notional * val * self.df
@pv.setter
def pv(self, val):
# use sigma_black as a starting point
self.pv_black = val
+ if self.sigma == 0.:
+ self.sigma = 1e-6
def handle(x):
self.sigma = x