diff options
| author | Guillaume Horel <guillaume.horel@gmail.com> | 2017-05-17 14:12:05 -0400 |
|---|---|---|
| committer | Guillaume Horel <guillaume.horel@gmail.com> | 2017-05-17 14:12:05 -0400 |
| commit | 1f09c1b4a66146d19c8f6ad5f616900a34457adb (patch) | |
| tree | 6f18aa720528660b7f10446c6a5d091e585b12d7 | |
| parent | 80c57d8132f43cb734442aa2e852413ef7658c19 (diff) | |
| download | pyisda-1f09c1b4a66146d19c8f6ad5f616900a34457adb.tar.gz | |
more C++
| -rw-r--r-- | pyisda/credit_index.pxd | 23 | ||||
| -rw-r--r-- | pyisda/credit_index.pyx | 482 |
2 files changed, 284 insertions, 221 deletions
diff --git a/pyisda/credit_index.pxd b/pyisda/credit_index.pxd index c6a76ab..3da57f9 100644 --- a/pyisda/credit_index.pxd +++ b/pyisda/credit_index.pxd @@ -2,18 +2,21 @@ from legs cimport TContingentLeg, TFeeLeg from date cimport TDate from curve cimport TCurve, TRatePt, shared_ptr from libcpp.vector cimport vector -from libcpp.map cimport map +from libcpp.unordered_map cimport unordered_map from libcpp.string cimport string cdef class CurveList: - cdef TDate _base_date - cdef vector[shared_ptr[TCurve]] _curves - cdef vector[double] _weights - cdef vector[double] _T - cdef map[string, size_t] _tickers + cdef TDate base_date + cdef public vector[double] weights + cdef vector[double] T + cdef unordered_map[string, shared_ptr[TCurve]] curves cdef class CreditIndex(CurveList): - cdef _start_date - cdef vector[TDate] _maturities - cdef TContingentLeg** _contingent_legs - cdef TFeeLeg** _fee_legs + cdef TDate start_date + cdef vector[TDate] maturities + cdef TContingentLeg** contingent_legs + cdef TFeeLeg** fee_legs + +cdef extern from "<algorithm>" namespace "std" nogil: + Iter find[Iter, T](Iter first, Iter last, const T& val) + Iter lower_bound[Iter, T](Iter first, Iter last, const T& value) diff --git a/pyisda/credit_index.pyx b/pyisda/credit_index.pyx index 48d188d..1474dd5 100644 --- a/pyisda/credit_index.pyx +++ b/pyisda/credit_index.pyx @@ -1,5 +1,7 @@ from libc.stdlib cimport malloc, free -from cython.operator cimport dereference as deref, preincrement as preinc +from libcpp.pair cimport pair +from cython.operator cimport dereference as deref + cimport cython from legs cimport (JpmcdsCdsContingentLegMake, JpmcdsCdsFeeLegMake, JpmcdsContingentLegPV, JpmcdsFeeLegPV, FeeLegAI, JpmcdsFeeLegFree) @@ -8,13 +10,15 @@ from curve cimport (SpreadCurve, JpmcdsCopyCurve, tweak_curve, YieldCurve, from date cimport pydate_to_TDate, TDate_to_pydate, ACT_365F from cdsone cimport JpmcdsStringToStubMethod, TStubMethod from date cimport ACT_365F +from dateutil.relativedelta import relativedelta cimport numpy as np np.import_array() import pandas as pd cdef class CurveList: @cython.cdivision(True) - def __init__(self, curves, double[:] weights=None): + @cython.boundscheck(False) + def __init__(self, list curves, double[:] weights=None): cdef: SpreadCurve sc size_t i @@ -23,45 +27,47 @@ cdef class CurveList: sc = <SpreadCurve>curves[0] else: raise TypeError("curves need to be a list of SpreadCurve") - self._T = vector[double](sc._thisptr.get().fNumItems) - self._base_date = sc._thisptr.get().fBaseDate - for i in range(self._T.size()): - self._T[i] = (sc._thisptr.get().fArray[i].fDate - self._base_date) / 365. - for i, sc in enumerate(curves): - self._curves.push_back(sc._thisptr) - self._tickers[sc.ticker.encode()] = i + self.T = vector[double](sc._thisptr.get().fNumItems) + self.base_date = sc._thisptr.get().fBaseDate + for i in range(self.T.size()): + self.T[i] = (sc._thisptr.get().fArray[i].fDate - self.base_date) / 365. + + for sc in curves: + self.curves[sc.ticker] = sc._thisptr if weights is not None: for i in range(weights.shape[0]): - self._weights.push_back(weights[i]) + self.weights.push_back(weights[i]) else: - self._weights = vector[double](self._curves.size(), 1./self._curves.size()) + self.weights = vector[double](self.curves.size(), 1./self.curves.size()) def __getitem__(self, str ticker): cdef: - map[string, size_t].iterator got = \ - self._tickers.find(ticker.encode()) + string ticker_cpp = ticker.encode() + unordered_map[string, shared_ptr[TCurve]].iterator got = \ + self.curves.find(ticker_cpp) SpreadCurve sc - if got == self._tickers.end(): + if got == self.curves.end(): raise KeyError(ticker) else: sc = SpreadCurve.__new__(SpreadCurve) - sc._thisptr = self._curves[deref(got).second] - sc.ticker = ticker + sc._thisptr = deref(got).second + sc.ticker = ticker_cpp return sc def items(self): ## would need to use a shared pointer to avoid a copy - cdef SpreadCurve sc - cdef map[string, size_t].const_iterator it = self._tickers.const_begin() - while it != self._tickers.const_end(): + cdef: + SpreadCurve sc + pair[string, shared_ptr[TCurve]] p + + for p in self.curves: sc = SpreadCurve.__new__(SpreadCurve) - sc._thisptr = self._curves[deref(it).second] - sc.ticker = (deref(it).first).decode() - yield (sc.ticker, sc) - preinc(it) + sc._thisptr = p.second + sc.ticker = p.first + yield (sc.ticker.decode(), sc) @property def curves(self): @@ -71,84 +77,105 @@ cdef class CurveList: cdef: list r = [] SpreadCurve sc - map[string, size_t].const_iterator it = self._tickers.const_begin() + pair[string, shared_ptr[TCurve]] p - while it != self._tickers.const_end(): + for p in self.curves: sc = SpreadCurve.__new__(SpreadCurve) - sc._thisptr = self._curves[deref(it).second] - sc.ticker = (deref(it).first).decode() + sc._thisptr = p.second + sc.ticker = p.first r.append(sc) - preinc(it) return r @curves.setter def curves(self, list l): - cdef size_t len_l = len(l) - if len_l != self._curves.size(): - self._curves.resize(len_l) + cdef: + size_t len_l = len(l) + size_t i + SpreadCurve c + + if len_l != self.curves.size(): + self.curves.clear() + + c = l[0] + self.T.resize(c._thisptr.get().fNumItems) + self.base_date = c._thisptr.get().fBaseDate + + for i in range(self.T.size()): + self.T[i] = (c._thisptr.get().fArray[i].fDate - self.base_date) / 365. - cdef size_t i for i, c in enumerate(l): - self._curves[i] = (<SpreadCurve?>c)._thisptr + self.curves[c.ticker] = c._thisptr cdef class CreditIndex(CurveList): - def __init__(self, start_date, maturities, curves, double[:] weights=None): + def __init__(self, start_date, maturities, list curves, double[:] weights=None): CurveList.__init__(self, curves, weights) - self._start_date = pydate_to_TDate(start_date) + self.start_date = pydate_to_TDate(start_date) for d in maturities: - self._maturities.push_back(pydate_to_TDate(d)) + self.maturities.push_back(pydate_to_TDate(d)) - self._contingent_legs = <TContingentLeg**> malloc(self._maturities.size() * + self.contingent_legs = <TContingentLeg**> malloc(self.maturities.size() * sizeof(TContingentLeg)) - self._fee_legs = <TFeeLeg**> malloc(self._maturities.size() * + self.fee_legs = <TFeeLeg**> malloc(self.maturities.size() * sizeof(TFeeLeg)) cdef TStubMethod stub_type if JpmcdsStringToStubMethod(b"f/s", &stub_type) != 0: raise ValueError("can't convert stub") cdef size_t i - for i in range(self._maturities.size()): - self._contingent_legs[i] = JpmcdsCdsContingentLegMake(self._start_date, - self._maturities[i], - 1., - True) + for i in range(self.maturities.size()): + self.contingent_legs[i] = JpmcdsCdsContingentLegMake(self.start_date, + self.maturities[i], + 1., + True) - self._fee_legs[i] = JpmcdsCdsFeeLegMake(self._start_date, - self._maturities[i], - True, - NULL, - &stub_type, - 1., - 1.0, - 3, #ACT_360 - <long>'M', # MODIFIED - b'NONE', - True) + self.fee_legs[i] = JpmcdsCdsFeeLegMake(self.start_date, + self.maturities[i], + True, + NULL, + &stub_type, + 1., + 1.0, + 3, #ACT_360 + <long>'M', # MODIFIED + b'NONE', + True) def __dealloc__(self): - if self._contingent_legs is not NULL: - for i in range(self._maturities.size()): - free(self._contingent_legs[i]) - if self._fee_legs is not NULL: - for i in range(self._maturities.size()): - JpmcdsFeeLegFree(self._fee_legs[i]) + if self.contingent_legs is not NULL: + for i in range(self.maturities.size()): + free(self.contingent_legs[i]) + if self.fee_legs is not NULL: + for i in range(self.maturities.size()): + JpmcdsFeeLegFree(self.fee_legs[i]) #@cython.initializedcheck(False) @cython.boundscheck(False) def pv_vec(self, step_in_date, value_date, YieldCurve yc, double recovery_rate): - cdef TDate step_in_date_c = pydate_to_TDate(step_in_date) - cdef TDate value_date_c = pydate_to_TDate(value_date) - cdef np.npy_intp[2] n = [self._curves.size(), self._maturities.size()] - cdef double accrued - FeeLegAI(self._fee_legs[0], self._base_date, &accrued) - cdef size_t i, j + cdef: + TDate step_in_date_c = pydate_to_TDate(step_in_date) + TDate value_date_c = pydate_to_TDate(value_date) + np.npy_intp[2] n = [self.curves.size(), self.maturities.size()] + double accrued + size_t i = 0, j = 0 + np.ndarray[np.float64_t,ndim=2] cl_pv = np.PyArray_EMPTY(2, n, np.NPY_DOUBLE, 0) + np.ndarray[np.float64_t,ndim=2] fl_pv = np.PyArray_EMPTY(2, n, np.NPY_DOUBLE, 0) + list tickers = [] + np.ndarray[np.int64_t,ndim=1] d = np.PyArray_EMPTY(1, &n[1], np.NPY_INT64, 0) + TDate maturity + TCurve* sc + pair[string,shared_ptr[TCurve]] p + + FeeLegAI(self.fee_legs[0], self.base_date, &accrued) + + for maturity in self.maturities: + d[j] = maturity - 134774 + j += 1 + j = 0 - cdef np.ndarray[np.float64_t,ndim=2] cl_pv = np.PyArray_EMPTY(2, n, np.NPY_DOUBLE, 0) - cdef np.ndarray[np.float64_t,ndim=2] fl_pv = np.PyArray_EMPTY(2, n, np.NPY_DOUBLE, 0) - cdef TCurve* sc - for i in range(self._curves.size()): - sc = self._curves[i].get() - for j in range(self._maturities.size()): - JpmcdsContingentLegPV(self._contingent_legs[j], + for p in self.curves: + sc = p.second.get() + tickers.append(p.first.decode()) + for j in range(self.maturities.size()): + JpmcdsContingentLegPV(self.contingent_legs[j], sc.fBaseDate, value_date_c, step_in_date_c, @@ -156,7 +183,7 @@ cdef class CreditIndex(CurveList): sc, recovery_rate, &cl_pv[i,j]) - JpmcdsFeeLegPV(self._fee_legs[j], + JpmcdsFeeLegPV(self.fee_legs[j], sc.fBaseDate, step_in_date_c, value_date_c, @@ -164,14 +191,16 @@ cdef class CreditIndex(CurveList): sc, True, &fl_pv[i,j]) + i += 1 + return pd.concat({'duration': pd.DataFrame(fl_pv - accrued, - columns=pd.DatetimeIndex(self.maturities), - index=self._tickers), + columns=d.view('M8[D]'), + index=tickers), 'protection_pv': pd.DataFrame(cl_pv, - columns=pd.DatetimeIndex(self.maturities), - index=self._tickers)}) + columns=d.view('M8[D]'), + index=tickers)}) @cython.boundscheck(False) def pv(self, step_in_date, value_date, maturity, YieldCurve yc not None, @@ -181,59 +210,27 @@ cdef class CreditIndex(CurveList): TDate step_in_date_c = pydate_to_TDate(step_in_date) TDate value_date_c = pydate_to_TDate(value_date) TDate maturity_c = pydate_to_TDate(maturity) - TContingentLeg* cl - TFeeLeg* fl - TStubMethod stub_type - - for i in range(self._maturities.size()): - if self._maturities[i] == maturity_c: - cl = self._contingent_legs[i] - fl = self._fee_legs[i] - break - else: - raise ValueError("maturity is not correct") - + unsigned long mask = 0 + vector[double] h + double accrued + pair[TContingentLeg,TFeeLeg] legs = get_legs(maturity_c, + self.start_date, + self.contingent_legs, + self.fee_legs, + self.maturities) - cdef bint* mask - cdef vector[double] h - cdef TCurve* tweaked_curve - if epsilon != 0: - mask = fill_mask(maturity_c, self._maturities, self._curves[0]) - if mask == NULL: + if epsilon != 0.: + mask = fill_mask(maturity_c, self.maturities, deref(self.curves.begin()).second) + if mask == 0: raise ValueError("maturity is not correct") - h = vector[double](self._T.size()) - tweaked_curve = JpmcdsCopyCurve(self._curves[0].get()) + h = vector[double](self.T.size()) + FeeLegAI(self.fee_legs[0], self.base_date, &accrued) - cdef: - double accrued, fl_pv, cl_pv, r = 0 - FeeLegAI(self._fee_legs[0], self._base_date, &accrued) + r = pv(self.curves, self.base_date, step_in_date_c, value_date_c, + yc._thisptr.get(), legs, recovery_rate, fixed_rate, + self.weights, epsilon, h, self.T, mask) - for i in range(self._curves.size()): - if epsilon != 0: - tweak_curve(self._curves[i].get(), tweaked_curve, epsilon, h, self._T, mask) - else: - tweaked_curve = self._curves[i].get() - JpmcdsContingentLegPV(cl, - self._base_date, - value_date_c, - step_in_date_c, - yc._thisptr.get(), - tweaked_curve, - recovery_rate, - &cl_pv) - JpmcdsFeeLegPV(fl, - self._base_date, - step_in_date_c, - value_date_c, - yc._thisptr.get(), - tweaked_curve, - True, - &fl_pv) - r += self._weights[i] * (cl_pv - (fl_pv - accrued) * fixed_rate) - if epsilon != 0: - JpmcdsFreeTCurve(tweaked_curve) - free(mask) - return r + return r - fixed_rate * accrued def theta(self, step_in_date, value_date, maturity, YieldCurve yc not None, double recovery_rate, double fixed_rate): @@ -241,55 +238,22 @@ cdef class CreditIndex(CurveList): TDate step_in_date_c = pydate_to_TDate(step_in_date) TDate value_date_c = pydate_to_TDate(value_date) TDate maturity_c = pydate_to_TDate(maturity) - TStubMethod stub_type - double accrued - - FeeLegAI(self._fee_legs[0], self._base_date, &accrued) - if JpmcdsStringToStubMethod(b"f/s", &stub_type) != 0: - raise ValueError("can't convert stub") - cdef: - TContingentLeg* cl = JpmcdsCdsContingentLegMake(self._start_date, - maturity_c, - 1., - True) - - TFeeLeg* fl = JpmcdsCdsFeeLegMake(self._start_date, - maturity_c, - True, - NULL, - &stub_type, - 1., - 1.0, - 3, #ACT_360 - <long>'M', # MODIFIED - b'NONE', - True) + vector[double] h + pair[TContingentLeg,TFeeLeg] legs = get_legs(maturity_c, + self.start_date, + self.contingent_legs, + self.fee_legs, + self.maturities) - vector[shared_ptr[TCurve]].iterator it = self._curves.begin() - vector[double].iterator w = self._weights.begin() - double fl_pv, cl_pv, r = 0 - - while it != self._curves.end(): - JpmcdsContingentLegPV(cl, - self._base_date, - value_date_c, - step_in_date_c, - yc._thisptr.get(), - deref(it).get(), - recovery_rate, - &cl_pv) - JpmcdsFeeLegPV(fl, - self._base_date, - step_in_date_c, - value_date_c, - yc._thisptr.get(), - deref(it).get(), - True, - &fl_pv) - r += deref(w) * (cl_pv - (fl_pv - accrued) * fixed_rate) - preinc(it) - preinc(w) - return r + cdef double old_pv = pv(self.curves, self.base_date, step_in_date_c, + value_date_c, yc._thisptr.get(), legs, + recovery_rate, fixed_rate, self.weights, + 0., h, self.T, 0) + maturity_c = pydate_to_TDate(maturity - relativedelta(years=1)) + return pv(self.curves, self.base_date, step_in_date_c, value_date_c, + yc._thisptr.get(), legs, recovery_rate, fixed_rate, + self.weights, 0., h, self.T, 0) - \ + old_pv + fixed_rate @cython.boundscheck(False) def duration(self, step_in_date, value_date, maturity, YieldCurve yc not None): @@ -298,67 +262,163 @@ cdef class CreditIndex(CurveList): TDate step_in_date_c = pydate_to_TDate(step_in_date) TDate value_date_c = pydate_to_TDate(value_date) TDate maturity_c = pydate_to_TDate(maturity) + double accrued + + FeeLegAI(self.fee_legs[0], self.base_date, &accrued) - cdef double accrued - FeeLegAI(self._fee_legs[0], self._base_date, &accrued) cdef: TFeeLeg* fl + size_t i - for i in range(self._maturities.size()): - if self._maturities[i] == maturity_c: - fl = self._fee_legs[i] + for i in range(self.maturities.size()): + if self.maturities[i] == maturity_c: + fl = self.fee_legs[i] break else: raise ValueError("maturity is not correct") - cdef: double fl_pv, r = 0 + pair[string,shared_ptr[TCurve]] p - for i in range(self._curves.size()): + i = 0 + for p in self.curves: JpmcdsFeeLegPV(fl, - self._base_date, + self.base_date, step_in_date_c, value_date_c, yc._thisptr.get(), - self._curves[i].get(), + p.second.get(), True, &fl_pv) - r += self._weights[i] * fl_pv + r += self.weights[i] * fl_pv + i += 1 return r - accrued @property def maturities(self): - cdef list r = [] - for i in range(self._maturities.size()): - r.append(TDate_to_pydate(self._maturities[i])) + cdef: + list r = [] + TDate maturity + + for maturity in self.maturities: + r.append(TDate_to_pydate(maturity)) return r - def tweak_portfolio(self, double epsilon, maturity): - cdef TDate maturity_c = pydate_to_TDate(maturity) - cdef bint* mask = fill_mask(maturity_c, self._maturities, self._curves[0]) - cdef vector[double] h = vector[double](self._T.size()) - cdef size_t i - cdef TCurve* sc - for i in range(self._curves.size()): - sc = self._curves[i].get() - tweak_curve(sc, sc, epsilon, h, self._T, mask) - free(mask) + def tweak_portfolio(self, double epsilon, maturity, inplace=True): + cdef: + TDate maturity_c = pydate_to_TDate(maturity) + unsigned long mask = fill_mask(maturity_c, self.maturities, + deref(self.curves.begin()).second) + vector[double] h = vector[double](self.T.size()) + TCurve* sc + pair[string, shared_ptr[TCurve]] p + + for p in self.curves: + sc = p.second.get() + tweak_curve(sc, sc, epsilon, h, self.T, mask) + +cdef unsigned long fill_mask(const TDate maturity, const vector[TDate]& maturities, + const shared_ptr[TCurve]& sc) nogil: + cdef: + TDate prev_maturity = 0 + size_t i + vector[TDate].const_iterator index = lower_bound(maturities.const_begin(), + maturities.const_end(), + maturity) + unsigned long mask = 0 + + if index == maturities.end(): + return 0 + elif index != maturities.begin(): + prev_maturity = deref(index-1) + + cdef TRatePt* it = sc.get().fArray + for i in range(sc.get().fNumItems): + if (it[i].fDate <= maturity ) and \ + (it[i].fDate > prev_maturity): + mask |= 1 << i + return mask + +cdef pair[TContingentLeg,TFeeLeg] get_legs(TDate maturity, + TDate start_date, + TContingentLeg** contingent_legs, + TFeeLeg** fee_legs, + vector[TDate]& maturities) nogil: + cdef: + pair[TContingentLeg,TFeeLeg] r + TStubMethod stub_type -cdef bint* fill_mask(TDate maturity, const vector[TDate]& maturities, - const shared_ptr[TCurve]& sc) nogil: - cdef TDate prev_maturity = 0 - cdef size_t i for i in range(maturities.size()): if maturities[i] == maturity: - if i > 0: - prev_maturity = maturities[i-1] break else: - return NULL - cdef bint* mask = <bint*>malloc(sc.get().fNumItems * sizeof(bint)) - cdef TRatePt* it = sc.get().fArray - for i in range(sc.get().fNumItems): - mask[i] = (it[i].fDate <= maturity ) and \ - (it[i].fDate > prev_maturity) - return mask + JpmcdsStringToStubMethod(b"f/s", &stub_type) + r.first = deref(JpmcdsCdsContingentLegMake(start_date, + maturity, + 1., + True)) + r.second = deref(JpmcdsCdsFeeLegMake(start_date, + maturity, + True, + NULL, + &stub_type, + 1., + 1.0, + 3, #ACT_360 + <long>'M', # MODIFIED + b'NONE', + True)) + return r + r.first = deref(contingent_legs[i]) + r.second = deref(fee_legs[i]) + return r + +cdef double pv(unordered_map[string,shared_ptr[TCurve]]& curves, + TDate base_date, + TDate step_in_date, + TDate value_date, + TCurve* yc, + pair[TContingentLeg,TFeeLeg]& legs, + double recovery_rate, + double fixed_rate, + vector[double]& weights, + double epsilon, + vector[double]& h, + vector[double]& T, + unsigned long mask) nogil: + cdef: + double fl_pv, cl_pv, r = 0 + TCurve* tweaked_curve + pair[string, shared_ptr[TCurve]] p + size_t i = 0 + TFeeLeg* fl + TContingentLeg* cl + + if epsilon != 0.: + tweaked_curve = JpmcdsCopyCurve(deref(curves.begin()).second.get()) + + for p in curves: + if epsilon != 0.: + tweak_curve(p.second.get(), tweaked_curve, epsilon, h, T, mask) + else: + tweaked_curve = p.second.get() + JpmcdsContingentLegPV(&legs.first, + base_date, + value_date, + step_in_date, + yc, + tweaked_curve, + recovery_rate, + &cl_pv) + JpmcdsFeeLegPV(&legs.second, + base_date, + step_in_date, + value_date, + yc, + tweaked_curve, + True, + &fl_pv) + r += weights[i] * (cl_pv - fl_pv * fixed_rate) + i += 1 + return r |
