aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--python/analytics/__init__.py1
-rw-r--r--python/analytics/index.py2
-rw-r--r--python/analytics/option.py53
-rw-r--r--python/swaption.py237
4 files changed, 59 insertions, 234 deletions
diff --git a/python/analytics/__init__.py b/python/analytics/__init__.py
index 90d1b1fe..1d5c0c0f 100644
--- a/python/analytics/__init__.py
+++ b/python/analytics/__init__.py
@@ -1 +1,2 @@
from .index import Index
+from .option import Swaption
diff --git a/python/analytics/index.py b/python/analytics/index.py
index 85ba3474..78b52fae 100644
--- a/python/analytics/index.py
+++ b/python/analytics/index.py
@@ -92,7 +92,7 @@ class Index():
if d > self.trade_date:
return 1
else:
- return math.exp( - self.flat_hazard * (d - self.trade_date)/365)
+ return math.exp( - self.flat_hazard * (d - self.trade_date).days/365)
def forward_pv(self, exercise_date):
"""This is default adjusted forward price at time exercise_date"""
diff --git a/python/analytics/option.py b/python/analytics/option.py
index 32f4f947..fe60d2d5 100644
--- a/python/analytics/option.py
+++ b/python/analytics/option.py
@@ -1,10 +1,57 @@
+import array
+import datetime
+import math
+import numpy as np
+import pandas as pd
+
from .black import black
from .utils import GHquad
from yieldcurve import roll_yc
from pandas.tseries.offsets import BDay
+from pyisda.curve import SpreadCurve
+from pyisda.flat_hazard import strike_vec
+from scipy.optimize import brentq
+from scipy.integrate import simps
+
+def g(index, spread : float, exercise_date : datetime.date, use_rolled_curve = True):
+ """ computes the strike clean price using the expected forward yield curve """
+ if use_rolled_curve:
+ rolled_curve = roll_yc(index._yc, exercise_date)
+ else:
+ rolled_curve = index._yc
+ step_in_date = exercise_date + datetime.timedelta(days=1)
+ exercise_date_settle = (pd.Timestamp(exercise_date) + 3* BDay()).date()
+ sc = SpreadCurve(exercise_date, rolled_curve, index.start_date,
+ step_in_date, exercise_date_settle,
+ [index.end_date], array.array('d', [spread * 1e-4]),
+ index.recovery)
+ a = index._fee_leg.pv(exercise_date, step_in_date, exercise_date_settle,
+ rolled_curve, sc, True)
+ return (spread - index.fixed_rate) * a *1e-4
+
+def calib(S0, fp, exercise_date : datetime.date, exercise_date_settle :datetime.date,
+ index, rolled_curve, tilt, w):
+ S = S0 * tilt * 1e-4
+ a, b = strike_vec(S, rolled_curve, exercise_date, exercise_date_settle,
+ index.start_date, index.end_date, index.recovery)
+ vec = a - index.fixed_rate * b * 1e-4
+ return np.inner(vec, w) - fp
+
+def ATMstrike(index, exercise_date : datetime.date):
+ exercise_date_settle = (pd.Timestamp(exercise_date) + 3* BDay()).date()
+ fp = index.forward_pv(exercise_date)
+ closure = lambda S: g(index, S, exercise_date) - fp
+ eta = 1.1
+ a = index.spread
+ b = index.spread * eta
+ while True:
+ if closure(b) > 0:
+ break
+ b *= eta
+ return brentq(closure, a, b)
-class Option:
- def __init__(self, index, exercise_date, strike, option_type="payer"):
+class Swaption:
+ def __init__(self, index, exercise_date : datetime.date, strike : float, option_type="payer"):
self.index = index
self._exercise_date = exercise_date
self._forward_yc = roll_yc(self.index._yc, self.exercise_date)
@@ -97,7 +144,7 @@ class Option:
if self._T:
return self._T
else:
- return year_frac(self.index.trade_date, self.exercise_date) + 1/365
+ return ((self.exercise_date - self.index.trade_date).days + 1)/365
@property
def gamma(self):
diff --git a/python/swaption.py b/python/swaption.py
index 68486cc8..2238abe6 100644
--- a/python/swaption.py
+++ b/python/swaption.py
@@ -1,232 +1,9 @@
-from pyisda.flat_hazard import strike_vec
-from quantlib.time.api import Date
-import array
-from scipy.optimize import brentq
-from scipy.integrate import simps
import datetime
-import numpy as np
-import pandas as pd
-from pandas.tseries.offsets import BDay
-from tranche_functions import GHquad
-from yieldcurve import roll_yc
-from scipy.stats import norm
+from analytics import Index, Swaption
-def year_frac(d1, d2, day_count_conv = "Actual/365"):
- """ compute the year fraction between two dates """
- if day_count_conv.lower() in ["actual/365", "act/365"]:
- return (d2-d1).days/365
- elif day_count_conv.lower() in ["actual/360", "act/360"]:
- return (d2-d1).days/360
-
-def calib(S0, fp, exercise_date, exercise_date_settle, index,
- rolled_curve, tilt, w):
- S = S0 * tilt * 1e-4
- a, b = strike_vec(S, rolled_curve, exercise_date, exercise_date_settle,
- index.start_date, index.end_date, index.recovery)
- vec = a - index.fixed_rate * b * 1e-4
- return np.inner(vec, w) - fp
-
-def g(index, spread, exercise_date, use_rolled_curve = True):
- """ computes the strike clean price using the expected forward yield curve """
- if use_rolled_curve:
- rolled_curve = roll_yc(index._yc, exercise_date)
- else:
- rolled_curve = index._yc
- step_in_date = exercise_date + datetime.timedelta(days=1)
- exercise_date_settle = (pd.Timestamp(exercise_date) + 3* BDay()).date()
- sc = SpreadCurve(exercise_date, rolled_curve, index.start_date,
- step_in_date, exercise_date_settle,
- [index.end_date], array.array('d', [spread * 1e-4]),
- index.recovery)
- a = index._fee_leg.pv(exercise_date, step_in_date, exercise_date_settle,
- rolled_curve, sc, True)
- return (spread - index.fixed_rate) * a *1e-4
-
-def ATMstrike(index, exercise_date):
- exercise_date_settle = (pd.Timestamp(exercise_date) + 3* BDay()).date()
- fp = index.forward_pv(exercise_date)
- closure = lambda S: g(index, S, exercise_date) - fp
- eta = 1.1
- a = index.spread
- b = index.spread * eta
- while True:
- if closure(b) > 0:
- break
- b *= eta
- return brentq(closure, a, b)
-
-class Option:
- def __init__(self, index, exercise_date, strike, option_type="payer"):
- self.index = index
- self._exercise_date = exercise_date
- self._forward_yc = roll_yc(self.index._yc, self.exercise_date)
- self.exercise_date_settle = (pd.Timestamp(self.exercise_date) + 3* BDay()).date()
- self._T = None
- self.strike = strike
- self.option_type = option_type.lower()
- self._Z, self._w = GHquad(50)
- self.notional = 1
-
- @property
- def exercise_date(self):
- return self._exercise_date
-
- @exercise_date.setter
- def exercise_date(self, d : datetime.date):
- self._exercise_date = d
- self.exercise_date_settle = (pd.Timestamp(d) + 3* BDay()).date()
- self._forward_yc = roll_yc(self.index._yc, self.exercise_date)
-
- @property
- def pv(self):
- fp = self.index.forward_pv(self.exercise_date) / self.index.notional
- T = self.T
- tilt = np.exp(-self.sigma**2/2 * T + self.sigma * self._Z * math.sqrt(T))
- rolled_curve = roll_yc(self.index._yc, self.exercise_date)
- args = (fp, self.exercise_date, self.exercise_date_settle,
- self.index, self._forward_yc, tilt, self._w)
- eta = 1.1
- a = self.index.spread
- b = self.index.spread * eta
- while True:
- if calib(*((b,) + args)) > 0:
- break
- b *= eta
-
- S0 = brentq(calib, a, b, args)
-
- G = g(self.index, self.strike, self.exercise_date)
- if T == 0:
- pv = self.notional * (g(self.index, self.index.spread, self.exercise_date) - G)
- if self.option_type == "payer":
- return pv if self.index.spread > self.strike else 0
- else:
- return - pv if self.index.spread < self.strike else 0
-
- Zstar = (math.log(self.strike/S0) + self.sigma**2/2 * T) / \
- (self.sigma * math.sqrt(T))
-
- if self.option_type == "payer":
- Z = Zstar + np.logspace(0, 1.5, 300) - 1
- elif self.option_type == "receiver":
- Z = Zstar - np.logspace(0, 1.5, 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))
- a, b = strike_vec(S * 1e-4, rolled_curve, self.exercise_date,
- self.exercise_date_settle,
- self.index.start_date, self.index.end_date, self.index.recovery)
- val = ((a - b * self.index.fixed_rate*1e-4) - G) * 1/math.sqrt(2*math.pi) * np.exp(-Z**2/2)
- df_scale = self.index._yc.discount_factor(self.exercise_date_settle)
- return self.notional * simps(val, Z) * df_scale
-
- @property
- def pv2(self):
- G = g(self.index, self.strike, self.exercise_date)
- fp = self.index.forward_pv(self.exercise_date) / self.index.notional
- forward_annuity = self.index.forward_annuity(self.exercise_date)
- DA_forward_spread = fp / forward_annuity + self.index.fixed_rate * 1e-4
- strike_tilde = self.index.fixed_rate * 1e-4 + G / forward_annuity
- return forward_annuity * black(DA_forward_spread,
- strike_tilde,
- self.T,
- self.sigma,
- self.option_type) * self.notional
-
- @property
- def delta(self):
- old_index_pv = self.index.pv
- old_pv = self.pv
- self.index.spread += 0.1
- notional_ratio = self.index.notional/self.notional
- delta = (self.pv - old_pv)/(self.index.pv - old_index_pv) * notional_ratio
- self.index.spread -= 0.1
- return delta
-
-
- @property
- def T(self):
- if self._T:
- return self._T
- else:
- return year_frac(self.index.trade_date, self.exercise_date) + 1/365
-
- @property
- def gamma(self):
- pass
-
- @property
- def theta(self):
- old_pv = self.pv
- self._T = self.T - 1/365
- theta = self.pv - old_pv
- self._T = None
- return theta
-
- @property
- def vega(self):
- old_pv = self.pv
- self.sigma += 0.01
- vega = self.pv - old_pv
- self.sigma -= 0.01
- return vega
-
-def d1(F, K, sigma, T):
- return (np.log(F / K) + sigma**2 * T / 2) / (sigma * np.sqrt(T))
-
-def d2(F, K, sigma, T):
- return d1(F, K, sigma, T) - sigma * np.sqrt(T)
-
-def black(F, K, T, sigma, option_type = "payer"):
- chi = 1 if option_type == "payer" else -1
- if option_type == "payer":
- return F * norm.cdf(d1(F, K, sigma, T)) - K * norm.cdf(d2(F, K, sigma, T))
- else:
- return K * norm.cdf(- d2(F, K, sigma, T)) - F * norm.cdf(- d1(F, K, sigma, T))
-
-def option(index, exercise_date, sigma, K, option_type="payer"):
- """ computes the pv of an option using Pedersen's model """
- fp = index.forward_pv(exercise_date)/index.notional
- #forward_yc = yield_curve.expected_forward_curve(exercise_date)
- #expiry is end of day (not sure if this is right)
- T = year_frac(index.trade_date, exercise_date)
- Z, w = GHquad(50)
- tilt = np.exp(-sigma**2/2 * T + sigma * Z * math.sqrt(T))
- exercise_date_settle = (pd.Timestamp(exercise_date) + 3* BDay()).date()
- args = (fp, exercise_date, exercise_date_settle, index, tilt, w)
- ## atm forward is greater than spread
- eta = 1.1
- a = index.spread
- b = index.spread * eta
- while True:
- if calib(*((b,) + args)) > 0:
- break
- b *= eta
- S0 = brentq(calib, a, b, args)
- S = S0 * tilt
- G = g(index, K, exercise_date)
- handle = lambda Z: g(index, S0 * math.exp(-sigma**2/2 * T + sigma * Z * math.sqrt(T)),
- exercise_date) - G
- Zstar = brentq(handle, -3, 3)
- if option_type.lower() == "payer":
- Z = Zstar + np.logspace(0, 1.1, 300) - 1
- elif option_type.lower() == "receiver":
- Z = Zstar - np.logspace(0, 1.1, 300) + 1
- else:
- raise ValueError("option_type needs to be either 'payer' or 'receiver'")
- S = S0 * np.exp(-sigma**2/2 * T + sigma * Z * math.sqrt(T))
- a, b = strike_vec(S, index._yc, exercise_date, exercise_date_settle,
- index.start_date, index.end_date, index.recovery)
- val = ((a - b * index.fixed_rate)/df - G) * 1/math.sqrt(2*math.pi) * np.exp(-Z**2/2)
- return simps(val, Z) * yield_curve.discount_factor(exercise_date_settle)
-
-if __name__ == "__main__":
- import datetime
- from analytics import Index
- from swaption import Option
-
- ig27_5yr = Index.from_name('ig', 27, '5yr', datetime.date(2016, 10, 24))
- ig27_5yr.spread = 74
- payer = Option(ig27_5yr, datetime.date(2016, 12, 21), 65)
- payer.sigma = 0.428
- payer.notional = 10e6
+ig27_5yr = Index.from_name('ig', 27, '5yr', datetime.date(2016, 10, 24))
+ig27_5yr.spread = 74
+payer = Swaption(ig27_5yr, datetime.date(2016, 12, 21), 65)
+payer.sigma = 0.428
+payer.notional = 10e6
+print(payer.pv)