def test_roll(self): """ TODO: include assert for each scenario shift :return: """ self.sdate = datetime.datetime(2018, 1, 8) self.value_date = self.sdate.strftime('%d/%m/%Y') self.verbose = 0 self.is_buy_protection = 0 # build imm_dates TODO: hide this away internally? self.imm_dates = [ f[1] for f in imm_date_vector(start_date=self.sdate, tenor_list=self.tenor_list) ] f = cds_all_in_one(self.trade_date, self.effective_date, self.maturity_date, self.value_date, self.accrual_start_date, self.recovery_rate, self.coupon, self.notional, self.is_buy_protection, self.swap_rates, self.swap_tenors, self.swap_maturity_dates, self.credit_spreads, self.credit_spread_tenors, self.spread_roll_tenors, self.imm_dates, self.scenario_shifts, self.verbose)
def test_single_factor_shift(self): """ method to test roll and roll shocks """ """ base case roll test; '1D' - moves the roll date one day past maturity '-1D' - moves the stepin date one day closer to maturity '-1W' - moves stepin one week closer to maturity '-1M' - 1 month closer '-6M' - 6 months closer '-1Y' - 1 whole year closer '-5Y' - 5 whole years closer :return: """ self.sdate = datetime.datetime(2018, 1, 8) self.value_date = self.sdate.strftime('%d/%m/%Y') self.verbose = 0 self.is_buy_protection = 0 # used to generate and shock roll dataset # both positive and negative; -1D moves the maturity date one day closer to stepIn # whilst 1D pushes the scheduled termination date further out increasing the TTM. self.spread_roll_tenors = ['1D', '-1D', '-1W', '-1M', '-6M', '-1Y'] self.scenario_shifts = [0] # build imm_dates TODO: hide this away internally somewhere? self.imm_dates = [f[1] for f in imm_date_vector(start_date=self.sdate, tenor_list=self.tenor_list)] f = cds_all_in_one(self.trade_date, self.effective_date, self.maturity_date, self.value_date, self.accrual_start_date, self.recovery_rate, self.coupon, self.notional, self.is_buy_protection, self.swap_rates, self.swap_tenors, self.swap_maturity_dates, self.credit_spreads, self.credit_spread_tenors, self.spread_roll_tenors, self.imm_dates, self.scenario_shifts, self.verbose) pv_clean = f[0][1] # self.spread_roll_tenors zero shift self.spread_pv_clean_result = 0.03234206758048001 # self.spread_roll_tenors zero shift self.spread_roll_tenors_results = [0.03234206758048001, 0.03230149347632522, 0.03205803736866009, 0.031124617333038864, 0.024909464222462, 0.017504780143193628] for test_value, result_value in zip(list(f[3]), self.spread_roll_tenors_results): self.assertAlmostEqual(pv_clean-test_value, self.spread_pv_clean_result-result_value, 4)
def test_sell_protection(self): """ method to test sell protection single name CDS """ self.sdate = datetime.datetime(2018, 1, 8) self.value_date = self.sdate.strftime('%d/%m/%Y') self.verbose = 0 self.is_buy_protection = 0 # build imm_dates TODO: hide this away internally? self.imm_dates = [ f[1] for f in imm_date_vector(start_date=self.sdate, tenor_list=self.tenor_list) ] f = cds_all_in_one(self.trade_date, self.effective_date, self.maturity_date, self.value_date, self.accrual_start_date, self.recovery_rate, self.coupon, self.notional, self.is_buy_protection, self.swap_rates, self.swap_tenors, self.swap_maturity_dates, self.credit_spreads, self.credit_spread_tenors, self.spread_roll_tenors, self.imm_dates, self.scenario_shifts, self.verbose) # expand tuple pv_dirty, pv_clean, ai, cs01, dv01, duration_in_milliseconds = f[0] pvbp6m, pvbp1y, pvbp2y, pvbp3y, pvbp4y, pvbp5y, pvbp7y, pvbp10y = f[1] # sell protection +ve npv # sell protection +ve npv # sell protection -ve cs01 # sell protection -ve dv01 self.assertAlmostEqual(1.23099324435, pv_dirty) self.assertAlmostEqual(1.19210435546, pv_clean) self.assertAlmostEqual(0.0388888888889, ai) self.assertAlmostEqual(-14014.5916905, cs01 * 1.0e6) self.assertAlmostEqual(-131.61798715, dv01 * 1.0e6) #print("cob_date: {0} pv_dirty: {1} pv_clean: {2} ai: {3} cs01: {4} dv01: {5} wall_time: {6}".format( # self.value_date,pv_dirty, pv_clean, ai, cs01 * 1e6, dv01 * 1e6, duration_in_milliseconds)) six_month_equivalent_notional = -cs01 / pvbp6m one_year_equivalent_notional = -cs01 / pvbp1y two_year_equivalent_notional = -cs01 / pvbp2y three_year_equivalent_notional = -cs01 / pvbp3y four_year_equivalent_notional = -cs01 / pvbp4y five_year_equivalent_notional = -cs01 / pvbp5y seven_year_equivalent_notional = -cs01 / pvbp7y ten_year_equivalent_notional = -cs01 / pvbp10y self.assertAlmostEqual(307.495318062, six_month_equivalent_notional) self.assertAlmostEqual(145.357246478, one_year_equivalent_notional) self.assertAlmostEqual(70.8820514668, two_year_equivalent_notional) self.assertAlmostEqual(46.8826264701, three_year_equivalent_notional) self.assertAlmostEqual(35.1120297467, four_year_equivalent_notional) self.assertAlmostEqual(28.1221873429, five_year_equivalent_notional) self.assertAlmostEqual(20.2184428985, seven_year_equivalent_notional) self.assertAlmostEqual(14.4072625131, ten_year_equivalent_notional)
def test_1day_roll_buy_protection_cds_shift(self): """ method to test roll simple 1 day clean roll pv """ """ base case roll test; '-1D' - moves the stepin date one day closer to maturity :return: """ self.sdate = datetime.datetime(2018, 1, 8) self.value_date = self.sdate.strftime('%d/%m/%Y') self.verbose = 0 self.is_buy_protection = 1 # used to generate and shock roll dataset self.spread_roll_tenors = ['-1D', '-1W', '-1M', '-1Y'] self.scenario_shifts = [0] # build imm_dates TODO: hide this away internally somewhere? self.imm_dates = [f[1] for f in imm_date_vector(start_date=self.sdate, tenor_list=self.tenor_list)] f = cds_all_in_one(self.trade_date, self.effective_date, self.maturity_date, self.value_date, self.accrual_start_date, self.recovery_rate, self.coupon, self.notional, self.is_buy_protection, self.swap_rates, self.swap_tenors, self.swap_maturity_dates, self.credit_spreads, self.credit_spread_tenors, self.spread_roll_tenors, self.imm_dates, self.scenario_shifts, self.verbose) pv_clean = f[0][1] # self.spread_roll_tenors zero shift self.spread_pv_clean_result = -0.03234206758048001 self.spread_roll_tenors_results = [-0.03230149347632522, -0.03205803736866009, -0.031124617333038864, -0.017504780143193628] for test_value, result_value in zip(list(f[3]), self.spread_roll_tenors_results): self.assertAlmostEqual(pv_clean-test_value, self.spread_pv_clean_result-result_value, 4)
def test_sell_protection(self): self.sdate = datetime.datetime(2018, 1, 8) self.value_date = self.sdate.strftime('%d/%m/%Y') self.verbose = 0 self.is_buy_protection = 0 # build imm_dates TODO: hide this away internally? self.imm_dates = [ f[1] for f in imm_date_vector(start_date=self.sdate, tenor_list=self.tenor_list) ] f = cds_all_in_one(self.trade_date, self.effective_date, self.maturity_date, self.value_date, self.accrual_start_date, self.recovery_rate, self.coupon, self.notional, self.is_buy_protection, self.swap_rates, self.swap_tenors, self.swap_maturity_dates, self.credit_spreads, self.credit_spread_tenors, self.spread_roll_tenors, self.imm_dates, self.scenario_shifts, self.verbose) # expand tuple pv_dirty, pv_clean, ai, cs01, dv01, duration_in_milliseconds = f[0] pvbp6m, pvbp1y, pvbp2y, pvbp3y, pvbp4y, pvbp5y, pvbp7y, pvbp10y = f[1] # sell protection +ve npv # sell protection +ve npv # sell protection -ve cs01 # sell protection -ve dv01 self.assertAlmostEquals(-22.3413350001, pv_dirty) self.assertAlmostEquals(-22.380223889, pv_clean) self.assertAlmostEquals(0.0388888888889, ai) self.assertAlmostEquals(-2612.54681047, cs01 * 1.0e6) self.assertAlmostEquals(1452.684883, dv01 * 1.0e6)
def test_sell_proptection_roll(self): """ method to test sell protection single name CDS stressed clean roll pv """ """ C:\github\CreditDefaultSwapPricer\isda\tests>python TestCdsPricer.py .-50 (1.2885563078769746, 1.2867443110180663, 1.275872200629506, 1.2341941013410378, 0.9569132641711624, 0.627070135500295, -0.0) -10 (1.2113774003109585, 1.209675487125174, 1.199463788179147, 1.160315827437148, 0.8998078545285471, 0.5897852857014516, -0.0) 0 (1.1921043554642177, 1.1904299019458315, 1.1803829401914545, 1.141866141475618, 0.8855434271776345, 0.5804691779283648, -0.0) 10 (1.1728399756302652, 1.1711929573119526, 1.1613105866742064, 1.1234244016646082, 0.8712837666636524, 0.5711551111334601, -0.0) 20 (1.1535842569100503, 1.1519646493398115, 1.1422467238468759, 1.10499060457683, 0.8570288713937431, 0.5618430848703984, -0.0) 50 (1.0958690284470913, 1.0943315065849426, 1.085106041654529, 1.0497368354280734, 0.8142927611229521, 0.5339192448040203, -0.0) 150 (0.9040463035118479, 0.9027808181324095, 0.8951874402865281, 0.8660724744683601, 0.6721481462760723, 0.44097222887852516, -0.0) 100 (0.9998498397804708, 0.9984486399470907, 0.9900410322567444, 0.9578057553878155, 0.743161067035703, 0.48742028035749574, -0.0) ... ---------------------------------------------------------------------- Ran 4 tests in 0.343s :return: """ self.sdate = datetime.datetime(2018, 1, 8) self.value_date = self.sdate.strftime('%d/%m/%Y') self.verbose = 0 self.is_buy_protection = 0 self.spread_roll_tenors_results = { -50: [ 1.2885563078769746, 1.2867443110180663, 1.275872200629506, 1.2341941013410378, 0.9569132641711624, 0.627070135500295, -0.0 ], -10: [ 1.2113774003109585, 1.209675487125174, 1.199463788179147, 1.160315827437148, 0.8998078545285471, 0.5897852857014516, -0.0 ], 0: [ 1.1921043554642177, 1.1904299019458315, 1.1803829401914545, 1.141866141475618, 0.8855434271776345, 0.5804691779283648, -0.0 ], 10: [ 1.1728399756302652, 1.1711929573119526, 1.1613105866742064, 1.1234244016646082, 0.8712837666636524, 0.5711551111334601, -0.0 ], 20: [ 1.1535842569100503, 1.1519646493398115, 1.1422467238468759, 1.10499060457683, 0.8570288713937431, 0.5618430848703984, -0.0 ], 50: [ 1.0958690284470913, 1.0943315065849426, 1.085106041654529, 1.0497368354280734, 0.8142927611229521, 0.5339192448040203, -0.0 ], 100: [ 0.9998498397804708, 0.9984486399470907, 0.9900410322567444, 0.9578057553878155, 0.743161067035703, 0.48742028035749574, -0.0 ], 150: [ 0.9040463035118479, 0.9027808181324095, 0.8951874402865281, 0.8660724744683601, 0.6721481462760723, 0.44097222887852516, -0.0 ] } # build imm_dates TODO: hide this away internally? self.imm_dates = [ f[1] for f in imm_date_vector(start_date=self.sdate, tenor_list=self.tenor_list) ] f = cds_all_in_one(self.trade_date, self.effective_date, self.maturity_date, self.value_date, self.accrual_start_date, self.recovery_rate, self.coupon, self.notional, self.is_buy_protection, self.swap_rates, self.swap_tenors, self.swap_maturity_dates, self.credit_spreads, self.credit_spread_tenors, self.spread_roll_tenors, self.imm_dates, self.scenario_shifts, self.verbose) for scenario, test_value_list in enumerate(f[3:]): for test_value, result_value in zip( test_value_list, self.spread_roll_tenors_results[ self.scenario_shifts[scenario]]): self.assertAlmostEqual(test_value, result_value, 4)
def test_sell_protection_par_spread(self): self.sdate = datetime.datetime(2018, 1, 8) self.value_date = self.sdate.strftime('%d/%m/%Y') self.verbose = 0 self.is_buy_protection = 0 # build imm_dates TODO: hide this away internally? self.imm_dates = [ f[1] for f in imm_date_vector(start_date=self.sdate, tenor_list=self.tenor_list) ] f = cds_all_in_one(self.trade_date, self.effective_date, self.maturity_date, self.value_date, self.accrual_start_date, self.recovery_rate, self.coupon, self.notional, self.is_buy_protection, self.swap_rates, self.swap_tenors, self.swap_maturity_dates, self.credit_spreads, self.credit_spread_tenors, self.spread_roll_tenors, self.imm_dates, self.scenario_shifts, self.verbose) # expand tuple pv_dirty, pv_clean, ai, cs01, dv01, duration_in_milliseconds = f[0] pvbp6m, pvbp1y, pvbp2y, pvbp3y, pvbp4y, pvbp5y, pvbp7y, pvbp10y = f[1] ps_1m, ps_2m, ps_3M, ps_6M, ps_9M, ps_1Y, ps_2Y, ps_3Y, ps_4Y, ps_5Y, ps_6Y, ps_7Y, ps_8Y, ps_9Y, ps_10Y = f[ 2] self.assertAlmostEquals(0.00274826727324, ps_1m) self.assertAlmostEquals(0.00274883148583, ps_2m) self.assertAlmostEquals(0.00274929868985, ps_3M) self.assertAlmostEquals(0.00274939866579, ps_6M) self.assertAlmostEquals(0.00274936653181, ps_9M) self.assertAlmostEquals(0.00274937754343, ps_1Y) self.assertAlmostEquals(0.00274932944417, ps_2Y) self.assertAlmostEquals(0.00274932454643, ps_3Y) self.assertAlmostEquals(0.00274932165857, ps_4Y) self.assertAlmostEquals(0.0027493199385, ps_5Y) self.assertAlmostEquals(0.00274926894167, ps_6Y) self.assertAlmostEquals(0.00274932296072, ps_7Y) self.assertAlmostEquals(0.00274925367015, ps_8Y) self.assertAlmostEquals(0.00274927195173, ps_9Y) self.assertAlmostEquals(0.00274933238284, ps_10Y) # sell protection +ve npv # sell protection +ve npv # sell protection -ve cs01 # sell protection -ve dv01 self.assertAlmostEquals(1.23099324435, pv_dirty) self.assertAlmostEquals(1.19210435546, pv_clean) self.assertAlmostEquals(0.0388888888889, ai) self.assertAlmostEquals(-14014.5916905, cs01 * 1.0e6) self.assertAlmostEquals(-131.61798715, dv01 * 1.0e6) six_month_equivalent_notional = -cs01 / pvbp6m one_year_equivalent_notional = -cs01 / pvbp1y two_year_equivalent_notional = -cs01 / pvbp2y three_year_equivalent_notional = -cs01 / pvbp3y four_year_equivalent_notional = -cs01 / pvbp4y five_year_equivalent_notional = -cs01 / pvbp5y seven_year_equivalent_notional = -cs01 / pvbp7y ten_year_equivalent_notional = -cs01 / pvbp10y self.assertAlmostEquals(307.495318062, six_month_equivalent_notional) self.assertAlmostEquals(145.357246478, one_year_equivalent_notional) self.assertAlmostEquals(70.8820514668, two_year_equivalent_notional) self.assertAlmostEquals(46.8826264701, three_year_equivalent_notional) self.assertAlmostEquals(35.1120297467, four_year_equivalent_notional) self.assertAlmostEquals(28.1221873429, five_year_equivalent_notional) self.assertAlmostEquals(20.2184428985, seven_year_equivalent_notional) self.assertAlmostEquals(14.4072625131, ten_year_equivalent_notional)
def test_two_factor_shift(self): """ method to test roll and roll shocks generate a surface of pv change """ """ roll and roll shocks generate a surface of pv change; for each shock the vector of roll tenors is evaluated The example below is a typical output from shock vector [-10, 0, 10] which translates internally in the scenario engine; via the formula below; you can see that -10 is translated into a -10/100 or -0.1 * spread_rate value and merged into the original spread_rates. This has the overall affect to move the spread_rate lower by 10% ahead of shocking the spread_rates[r] + spread_rates[r] * scenario_tenors[s]/100; -10 (0.024377398537551408, 0.017571131660956488, 0.007359432714929524, -0.0317885280270698, -0.2922965009356705, -0.602319069762766, -1.1921043554642177) 0 (0.0050220109323667605, -0.0016744535183860904, -0.01172141527276311, -0.0502382139885996, -0.3065609282865831, -0.6116351775358528, -1.1921043554642177) 10 (-0.014324638086876014, -0.020911398152265066, -0.030793768790011236, -0.06867995379960933, -0.3208205888005652, -0.6209492443307575, -1.1921043554642177) The output above has been generated for 7 separate shocks to the time to maturity. It should be possible to compute an entire surface before and after the maturity date of each CDS instrument using this approach which can provide detailed information around the roll of each CDS contract. :return: """ self.sdate = datetime.datetime(2018, 1, 8) self.value_date = self.sdate.strftime('%d/%m/%Y') self.verbose = 0 self.is_buy_protection = 0 # used to generate and shock roll dataset self.spread_roll_tenors = ['1Y','1D', '-1D', '-1W', '-1M', '-6M', '-1Y'] self.scenario_shifts = [-10, 0, 10] # build imm_dates TODO: hide this away internally somewhere? self.imm_dates = [f[1] for f in imm_date_vector(start_date=self.sdate, tenor_list=self.tenor_list)] f = cds_all_in_one(self.trade_date, self.effective_date, self.maturity_date, self.value_date, self.accrual_start_date, self.recovery_rate, self.coupon, self.notional, self.is_buy_protection, self.swap_rates, self.swap_tenors, self.swap_maturity_dates, self.credit_spreads, self.credit_spread_tenors, self.spread_roll_tenors, self.imm_dates, self.scenario_shifts, self.verbose) pv_clean = f[0][1] # results to compare against self.spread_roll_tenors_results = {-10: [0.04387513244181401, 0.03306420268550876, 0.03302212083102655, 0.032769619019361866, 0.031801536455466885, 0.025356314262256027, 0.01767911535374796] , 0: [0.042111643439626215, 0.03234206758048001, 0.03230149347632522, 0.03205803736866009, 0.031124617333038864, 0.024909464222462, 0.017504780143193628] , 10:[0.040349886127636986, 0.03162025310504571, 0.03158118526362845, 0.031346766012800564, 0.030447975828985945, 0.024462724355530034, 0.01733046875873452]} # confirm that we have managed to generate the accurate number of scenario details # # confirm we have the same dataset for i, test_value_list in enumerate(f[3:]): for test_value, result_value in zip(test_value_list, self.spread_roll_tenors_results[self.scenario_shifts[i]]): self.assertAlmostEqual(test_value, result_value, 4)