From 73cbe54cd2c4319e4458f8c8d6d483bcfe6e567a Mon Sep 17 00:00:00 2001 From: Guillaume Horel Date: Tue, 5 Dec 2023 17:03:49 -0500 Subject: get rid of the defaulted vector --- c_layer/survival_curve.hpp | 5 + pyisda/credit_index.pxd | 1 - pyisda/credit_index.pyx | 387 ++++++++++++++++++++++++--------------------- pyisda/curve.pxd | 9 +- pyisda/curve.pyx | 10 +- 5 files changed, 230 insertions(+), 182 deletions(-) diff --git a/c_layer/survival_curve.hpp b/c_layer/survival_curve.hpp index 2275d9a..e6ee4f2 100644 --- a/c_layer/survival_curve.hpp +++ b/c_layer/survival_curve.hpp @@ -1,5 +1,6 @@ #include #include +#include struct CurveName { enum class Seniority: std::uint8_t { @@ -65,6 +66,10 @@ struct CurveName { } } + inline TDate* defaulted() { + return (TDate*)(name - sizeof(TDate)); + } + const char* name; std::string_view ticker; Seniority seniority; diff --git a/pyisda/credit_index.pxd b/pyisda/credit_index.pxd index 57fb8a4..0dba78f 100644 --- a/pyisda/credit_index.pxd +++ b/pyisda/credit_index.pxd @@ -12,7 +12,6 @@ cdef class CurveList: cdef vector[shared_ptr[char]] _curves cdef map[CurveName, size_t] names cdef vector[uint16_t] offset_recovery_rates - cdef vector[TDate] defaulted cdef double defaulted_weight cdef class CreditIndex(CurveList): diff --git a/pyisda/credit_index.pyx b/pyisda/credit_index.pyx index f1cb621..367000b 100644 --- a/pyisda/credit_index.pyx +++ b/pyisda/credit_index.pyx @@ -43,7 +43,6 @@ cdef CurveList_copy(CurveList orig, CurveList copy) noexcept: copy.base_date = orig.base_date copy._weights = orig._weights copy._curves.resize(orig._curves.size()) - copy.defaulted = orig.defaulted copy.offset_recovery_rates = orig.offset_recovery_rates copy.defaulted_weight = orig.defaulted_weight for p in orig.names: @@ -91,7 +90,6 @@ cdef class CurveList: self._curves.push_back(sc.buf) self.offset_recovery_rates.push_back(sc.offset_recovery_rates) self._weights.push_back(w) - self.defaulted.push_back(sc.defaulted) i += 1 else: self._weights[deref(it).second] += w @@ -109,9 +107,6 @@ cdef class CurveList: def __getitem__(self, tuple name not None): - if len(name) != 3: - raise TypeError("`name` needs to be a string, Seniority, DocClause triplet") - ticker, sen, doc_clause = name cdef: char* buf = malloc(len(name[0]) + 3) char* tmp @@ -125,16 +120,20 @@ cdef class CurveList: map[CurveName, size_t].iterator got = \ self.names.find(cn) SpreadCurve sc + size_t i if got == self.names.end(): free(buf) raise KeyError(name) else: + p = deref(got) + cn, i = p.first, p.second sc = SpreadCurve.__new__(SpreadCurve) - sc.buf = self._curves[deref(got).second] - sc.offset_name = deref(got).first.name - sc.buf.get() - sc.buf_size = sc.offset_name + deref(got).first.size() - sc.offset_recovery_rates = self.offset_recovery_rates[deref(got).second] - sc.defaulted = self.defaulted[deref(got).second] + sc.buf = self._curves[i] + sc.offset_name = cn.name - sc.buf.get() + sc.buf_size = sc.offset_name + cn.size() + sc.offset_recovery_rates = self.offset_recovery_rates[i] + sc.defaulted = deref(cn.defaulted()) + free(buf) return sc def items(self): @@ -142,16 +141,19 @@ cdef class CurveList: SpreadCurve sc pair[CurveName, size_t] p char* buf + CurveName cn + size_t i for p in self.names: + cn, i = p.first, p.second sc = SpreadCurve.__new__(SpreadCurve) - sc.buf = self._curves[p.second] + sc.buf = self._curves[i] buf = sc.buf.get() - sc.offset_name = p.first.name - buf - sc.buf_size = sc.offset_name + p.first.size() - sc.offset_recovery_rates = self.offset_recovery_rates[p.second] - sc.defaulted = self.defaulted[p.second] - yield ((sc.ticker, sc.seniority, sc.doc_clause), self._weights[p.second], sc) + sc.offset_name = cn.name - buf + sc.buf_size = sc.offset_name + cn.size() + sc.offset_recovery_rates = self.offset_recovery_rates[i] + sc.defaulted = deref(cn.defaulted()) + yield ((sc.ticker, sc.seniority, sc.doc_clause), self._weights[i], sc) @property @cython.wraparound(False) @@ -221,16 +223,19 @@ cdef class CurveList: SpreadCurve sc pair[CurveName, size_t] p char* buf + size_t i + CurveName cn for p in self.names: + cn, i = p.first, p.second sc = SpreadCurve.__new__(SpreadCurve) - sc.buf = self._curves[p.second] + sc.buf = self._curves[i] buf = sc.buf.get() - sc.offset_name = p.first.name - buf - sc.buf_size = sc.offset_name + p.first.size() - sc.offset_recovery_rates = self.offset_recovery_rates[p.second] - sc.defaulted = self.defaulted[p.second] - r.append((self._weights[p.second], sc)) + sc.offset_name = cn.name - buf + sc.buf_size = sc.offset_name + cn.size() + sc.offset_recovery_rates = self.offset_recovery_rates[i] + sc.defaulted = deref(cn.defaulted()) + r.append((self._weights[i], sc)) return r @curves.setter @@ -239,7 +244,6 @@ cdef class CurveList: self.names.clear() self._weights.clear() self.offset_recovery_rates.clear() - self.defaulted.clear() self.defaulted_weight = 0.0 cdef TDate temp = self.base_date CurveList.__init__(self, l) @@ -347,6 +351,7 @@ cdef class CreditIndex(CurveList): cdef: TDate step_in_date_c = pydate_to_TDate(step_in_date) TDate cash_settle_date_c = pydate_to_TDate(cash_settle_date) + TDate defaulted np.npy_intp[2] n n[0] = self._curves.size() n[1] = self._maturities.size() @@ -360,6 +365,8 @@ cdef class CreditIndex(CurveList): const TCurve* sc pair[CurveName, size_t] p double recovery_rate + CurveName cn + size_t curve_index for maturity in self._maturities: d[j] = maturity - 134774 @@ -367,30 +374,36 @@ cdef class CreditIndex(CurveList): j = 0 for p in self.names: - sc = self._curves[p.second].get() - tickers.append(p.first.full_ticker()) + cn, curve_index = p.first, p.second + sc = self._curves[curve_index].get() + tickers.append(cn.full_ticker()) # TODO: pick the actual recovery on the curve # this only works for flat recovery curve - recovery_rate = ((sc + self.offset_recovery_rates[p.second]))[0] - for j in range(self._maturities.size()): - JpmcdsContingentLegPV(self.contingent_legs[j], - sc.fBaseDate, - cash_settle_date_c, - step_in_date_c, - yc.get_TCurve(), - sc, - recovery_rate, - &cl_pv[i,j]) - JpmcdsFeeLegPV(self.fee_legs[j], - sc.fBaseDate, - step_in_date_c, - cash_settle_date_c, - yc.get_TCurve(), - sc, - True, - &fl_pv[i,j]) + recovery_rate = deref((sc + self.offset_recovery_rates[curve_index])) + defaulted = deref(cn.defaulted()) + if defaulted != -1: + for j in range(self._maturities.size()): + fl_pv[i, j] = 0.0 + cl_pv[i, j] = 1 - recovery_rate + else: + for j in range(self._maturities.size()): + JpmcdsContingentLegPV(self.contingent_legs[j], + sc.fBaseDate, + cash_settle_date_c, + step_in_date_c, + yc.get_TCurve(), + sc, + recovery_rate, + &cl_pv[i,j]) + JpmcdsFeeLegPV(self.fee_legs[j], + sc.fBaseDate, + step_in_date_c, + cash_settle_date_c, + yc.get_TCurve(), + sc, + True, + &fl_pv[i,j]) i += 1 - return pd.concat([pd.DataFrame(fl_pv, columns=d.view('M8[D]'), index=tickers), @@ -500,60 +513,68 @@ cdef class CreditIndex(CurveList): TDate maturity_short_c pair[TContingentLeg_ptr, TFeeLeg_ptr] legs_long, legs_short TDateInterval ivl + pair[CurveName, size_t] p Py_ssize_t i double carry double cl_pv_long, cl_pv_short, fl_pv_long, fl_pv_short np.npy_intp n = self._curves.size() np.ndarray arr = np.PyArray_EMPTY(1, &n, np.NPY_DOUBLE, 1) + CurveName cn + size_t curve_index cdef double[::1] r = arr JpmcdsMakeDateInterval(-1, b"Y", &ivl) JpmcdsDtFwdAny(maturity_c, &ivl, &maturity_short_c) - carry = (1 - self.defaulted_weight) * fixed_rate * 365 / 360 + carry = fixed_rate * 365 / 360 if maturity_short_c < step_in_date_c: return nan("") legs_long = get_legs(maturity_c, self._start_date, self.cal_file.c_str()) legs_short = get_legs(maturity_short_c, self._start_date, self.cal_file.c_str()) - - with nogil, parallel(): + with nogil: fl_pv_short = cl_pv_short = fl_pv_long = cl_pv_long = 0.0 - for i in prange(self._curves.size()): - curve = (self._curves[i].get()) - recovery_rate = (self._curves[i].get() + self.offset_recovery_rates[i]) - JpmcdsContingentLegPV(legs_long.first, - curve.fBaseDate, - cash_settle_date_c, - step_in_date_c, - yc.get_TCurve(), - curve, - deref(recovery_rate), - &cl_pv_long) - JpmcdsContingentLegPV(legs_short.first, - curve.fBaseDate, - cash_settle_date_c, - step_in_date_c, - yc.get_TCurve(), - curve, - deref(recovery_rate), - &cl_pv_short) - JpmcdsFeeLegPV(legs_long.second, - curve.fBaseDate, - step_in_date_c, - cash_settle_date_c, - yc.get_TCurve(), - curve, - True, - &fl_pv_long) - JpmcdsFeeLegPV(legs_short.second, - curve.fBaseDate, - step_in_date_c, - cash_settle_date_c, - yc.get_TCurve(), - curve, - True, - &fl_pv_short) - r[i] = self._weights[i] * (carry + cl_pv_long - cl_pv_short - (fl_pv_long - fl_pv_short ) * fixed_rate) + i = 0 + for p in self.names: + cn, curve_index = p.first, p.second + curve = (self._curves[curve_index].get()) + recovery_rate = (curve + self.offset_recovery_rates[curve_index]) + if deref(cn.defaulted()) != -1: + r[i] = 0.0 + else: + JpmcdsContingentLegPV(legs_long.first, + curve.fBaseDate, + cash_settle_date_c, + step_in_date_c, + yc.get_TCurve(), + curve, + deref(recovery_rate), + &cl_pv_long) + JpmcdsContingentLegPV(legs_short.first, + curve.fBaseDate, + cash_settle_date_c, + step_in_date_c, + yc.get_TCurve(), + curve, + deref(recovery_rate), + &cl_pv_short) + JpmcdsFeeLegPV(legs_long.second, + curve.fBaseDate, + step_in_date_c, + cash_settle_date_c, + yc.get_TCurve(), + curve, + True, + &fl_pv_long) + JpmcdsFeeLegPV(legs_short.second, + curve.fBaseDate, + step_in_date_c, + cash_settle_date_c, + yc.get_TCurve(), + curve, + True, + &fl_pv_short) + r[i] = carry + cl_pv_long - cl_pv_short - (fl_pv_long - fl_pv_short ) * fixed_rate + i += 1 return arr @@ -568,28 +589,29 @@ cdef class CreditIndex(CurveList): TCurve* sc double* recovery_rate - with nogil, parallel(): + with nogil: cl = JpmcdsCdsContingentLegMake(self._start_date, - maturity_c, - 1., - True) - cl_pv = 0.0 - for i in prange(self._curves.size()): - sc = self._curves[i].get() - recovery_rate = (sc + self.offset_recovery_rates[i]) - # FIXME: do something better - if isnan(deref(recovery_rate)): - preinc(recovery_rate) - JpmcdsContingentLegPV(cl, - self.base_date, - cash_settle_date_c, - step_in_date_c, - yc.get_TCurve(), - sc, - deref(recovery_rate), - &cl_pv) - r += self._weights[i] * cl_pv - free(cl) + maturity_c, + 1., + True) + with parallel(): + cl_pv = 0.0 + for i in prange(self._curves.size()): + sc = self._curves[i].get() + recovery_rate = (sc + self.offset_recovery_rates[i]) + # FIXME: do something better + if isnan(deref(recovery_rate)): + preinc(recovery_rate) + JpmcdsContingentLegPV(cl, + self.base_date, + cash_settle_date_c, + step_in_date_c, + yc.get_TCurve(), + sc, + deref(recovery_rate), + &cl_pv) + r += self._weights[i] * cl_pv + free(cl) return r def duration(self, step_in_date, cash_settle_date, maturity, YieldCurve yc not None): @@ -600,44 +622,45 @@ cdef class CreditIndex(CurveList): TDate maturity_c = pydate_to_TDate(maturity) TFeeLeg* fl TStubMethod stub_type + TCurve* sc size_t i int found + double fl_pv, r - found = get_maturity_index(maturity_c, self._maturities) - if found == -1: - JpmcdsStringToStubMethod(b"f/s", &stub_type) - fl = JpmcdsCdsFeeLegMake(self._start_date, - maturity_c, - True, - NULL, - &stub_type, - 1., - 1.0, - ACT_360, # ACT_360 - MODIFIED, # MODIFIED - self.cal_file.c_str(), - True) - else: - fl = self.fee_legs[found] - - cdef: - double fl_pv, r = 0 - shared_ptr[char] sc + with nogil: + found = get_maturity_index(maturity_c, self._maturities) + if found == -1: + JpmcdsStringToStubMethod(b"f/s", &stub_type) + fl = JpmcdsCdsFeeLegMake(self._start_date, + maturity_c, + True, + NULL, + &stub_type, + 1., + 1.0, + ACT_360, # ACT_360 + MODIFIED, # MODIFIED + self.cal_file.c_str(), + True) + else: + fl = self.fee_legs[found] + + with parallel(): + fl_pv = 0 + for i in prange(self._curves.size()): + sc = self._curves[i].get() + JpmcdsFeeLegPV(fl, + self.base_date, + step_in_date_c, + cash_settle_date_c, + yc.get_TCurve(), + sc, + True, + &fl_pv) + r += self._weights[i] * fl_pv - i = 0 - for sc in self._curves: - JpmcdsFeeLegPV(fl, - self.base_date, - step_in_date_c, - cash_settle_date_c, - yc.get_TCurve(), - sc.get(), - True, - &fl_pv) - r += self._weights[i] * fl_pv - i += 1 - if found == -1: - JpmcdsFeeLegFree(fl) + if found == -1: + JpmcdsFeeLegFree(fl) return r @property @@ -733,7 +756,7 @@ cdef class CreditIndex(CurveList): pair[CurveName, size_t] p np.npy_intp[2] n const TDate* schedule_ptr - size_t i, j = 0 + size_t i, j = 0, curve_index if schedule is None: schedule_ptr = self._maturities.const_data() @@ -752,17 +775,19 @@ cdef class CreditIndex(CurveList): np.ndarray spreads = np.PyArray_EMPTY(2, n, np.NPY_DOUBLE, 0) double[:, ::1] spreads_view = spreads TStubMethod stub_type + CurveName cn JpmcdsStringToStubMethod(b"f/s", &stub_type) with nogil: for p in self.names: - if self.defaulted[p.second] != -1: + cn, curve_index = p.first, p.second + if deref(cn.defaulted()) != -1: for i in range(n[1]): spreads_view[j, i] = nan("") j += 1 continue - sc = (self._curves[p.second].get()) + sc = (self._curves[curve_index].get()) JpmcdsCdsParSpreads( self.base_date, self.base_date + 1, @@ -777,7 +802,7 @@ cdef class CreditIndex(CurveList): self.cal_file.c_str(), yc.get_TCurve(), sc, - (sc + self.offset_recovery_rates[p.second]), + (sc + self.offset_recovery_rates[curve_index]), &spreads_view[j, 0]) j += 1 return spreads @@ -810,18 +835,20 @@ cdef class CreditIndex(CurveList): double* std_spreads = malloc( n * sizeof(double)) double w_sum, w_sum2 bint first_time = True + TDate defaulted JpmcdsStringToStubMethod(b"f/s", &stub_type) with nogil: for j in range(self._curves.size()): - if self.defaulted[j] != -1 and not exp_loss: - continue sc = self._curves[j].get() recovery_rates = (sc + self.offset_recovery_rates[j]) w = self._weights[j] + defaulted = deref((recovery_rates + sc.fNumItems)) + if defaulted != -1 and not exp_loss: + continue if exp_loss: - if self.defaulted[j] != -1: + if defaulted != -1: for i in range(n): spreads[i] = (1 - survival_prob(sc, self.base_date, schedule_ptr[i], 0., False)) * (1 - recovery_rates[i]) else: @@ -961,20 +988,23 @@ cdef double pv(const vector[shared_ptr[char]]& curves, TCurve *orig_curve size_t i = 0 double* recovery_rate + TDate defaulted if curves.size() == 0: return nan("") if epsilon == 0.: - with parallel(): - fl_pv = 0. - cl_pv = 0. - for i in prange(curves.size()): - tweaked_curve = (curves[i].get()) - recovery_rate = (curves[i].get() + offset_recovery_rates[i]) - # FIXME: do something better - if isnan(deref(recovery_rate)): - preinc(recovery_rate) - + for i in prange(curves.size()): + tweaked_curve = (curves[i].get()) + recovery_rate = (curves[i].get() + offset_recovery_rates[i]) + defaulted = deref((recovery_rate + tweaked_curve.fNumItems)) + # FIXME: do something better + if isnan(deref(recovery_rate)): + preinc(recovery_rate) + + if defaulted != -1: + fl_pv = 0.0 + cl_pv = 1.0 - deref(recovery_rate) + else: JpmcdsContingentLegPV(legs.first, base_date, cash_settle_date, @@ -991,36 +1021,39 @@ cdef double pv(const vector[shared_ptr[char]]& curves, tweaked_curve, True, &fl_pv) - r += weights[i] * (cl_pv - fl_pv * fixed_rate) + r += weights[i] * (cl_pv - fl_pv * fixed_rate) else: with parallel(): tweaked_curve = JpmcdsCopyCurve((curves[0].get())) - cl_pv = 0. - fl_pv = 0. for i in prange(curves.size()): orig_curve = (curves[i].get()) - tweaked_curve.fBaseDate = orig_curve.fBaseDate - tweak_curve(orig_curve, tweaked_curve, epsilon, mask) recovery_rate = (orig_curve + offset_recovery_rates[i]) + defaulted = deref((recovery_rate + orig_curve.fNumItems)) # FIXME: do something better if isnan(deref(recovery_rate)): preinc(recovery_rate) - - JpmcdsContingentLegPV(legs.first, - base_date, - cash_settle_date, - step_in_date, - yc, - tweaked_curve, - deref(recovery_rate), - &cl_pv) - JpmcdsFeeLegPV(legs.second, - base_date, - step_in_date, - cash_settle_date, - yc, - tweaked_curve, - True, - &fl_pv) + if defaulted != -1: + fl_pv = 0.0 + cl_pv = 1.0 - deref(recovery_rate) + else: + tweaked_curve.fBaseDate = orig_curve.fBaseDate + tweak_curve(orig_curve, tweaked_curve, epsilon, mask) + + JpmcdsContingentLegPV(legs.first, + base_date, + cash_settle_date, + step_in_date, + yc, + tweaked_curve, + deref(recovery_rate), + &cl_pv) + JpmcdsFeeLegPV(legs.second, + base_date, + step_in_date, + cash_settle_date, + yc, + tweaked_curve, + True, + &fl_pv) r += weights[i] * (cl_pv - fl_pv * fixed_rate) return r diff --git a/pyisda/curve.pxd b/pyisda/curve.pxd index db247cb..c824ac5 100644 --- a/pyisda/curve.pxd +++ b/pyisda/curve.pxd @@ -205,6 +205,7 @@ cdef extern from "survival_curve.hpp" nogil: CurveName(const char* buf) string full_ticker() size_t size() + TDate* defaulted() cdef class Curve: cdef shared_ptr[char] buf @@ -221,8 +222,8 @@ cdef class SpreadCurve(Curve): cdef uint16_t offset_recovery_rates cdef uint16_t offset_name - cdef inline double* recovery_rates_ptr(self) nogil - cdef inline char* name(self) nogil + cdef inline double* recovery_rates_ptr(self) noexcept nogil + cdef inline char* name(self) noexcept nogil cdef list fArray_to_list(const TRatePt* fArray, int fNumItems) noexcept @@ -243,3 +244,7 @@ cdef void forward_rates(double basis, const TRatePt* arr, const TDate base_date, cdef class CurveShock: cdef vector[int] t cdef vector[double] s + +#workaround cython performance bug +cdef to_seniority(Seniority val) noexcept +cdef to_doc_clause(DocClause val) noexcept diff --git a/pyisda/curve.pyx b/pyisda/curve.pyx index de0d0e6..adde159 100644 --- a/pyisda/curve.pyx +++ b/pyisda/curve.pyx @@ -940,10 +940,10 @@ cdef class SpreadCurve(Curve): cursor[1] = doc_clause strncpy(&cursor[2], c_ticker, ticker_len + 1) - cdef inline double* recovery_rates_ptr(self) nogil: + cdef inline double* recovery_rates_ptr(self) noexcept nogil: return (self.buf.get() + self.offset_recovery_rates) - cdef inline char* name(self) nogil: + cdef inline char* name(self) noexcept nogil: return self.buf.get() + self.offset_name @cython.boundscheck(False) @@ -1235,3 +1235,9 @@ cdef void _fill_curve(const TCurve* sc, const TDate* end_dates, int n_dates, cha t = (end_dates[i] - base_date)/365. it[i].fDate = end_dates[i] it[i].fRate = -JpmcdsLogForwardZeroPrice(sc, base_date, end_dates[i]) / t + +cdef inline to_seniority(Seniority val) noexcept: + return val + +cdef inline to_doc_clause(DocClause val) noexcept: + return val -- cgit v1.2.3-70-g09d2