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) for scenario, tt in enumerate(f[2:]): print self.scenario_shifts[scenario], tt
def test_buy_protection(self): self.sdate = datetime.datetime(2018, 1, 8) self.value_date = self.sdate.strftime('%d/%m/%Y') # 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) ] self.verbose = 0 f = cds_all_in_one_exclude_ir_tenor_dates( 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.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] # buy protection -ve npv # buy protection -ve npv # buy protection +ve cs01 # buy protection +ve dv01 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) 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 # print six_month_equivalent_notional, one_year_equivalent_notional, two_year_equivalent_notional, \ # three_year_equivalent_notional, four_year_equivalent_notional, five_year_equivalent_notional, \ # seven_year_equivalent_notional, ten_year_equivalent_notional 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_single_factor_shift(self): """ 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 self.spread_roll_tenors = [ '1D', '-1D', '-1W', '-1M', '-6M', '-1Y', '-5Y' ] 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) # self.spread_roll_tenors zero shift self.spread_roll_tenors_results = [ 0.0050220109323667605, -0.0016744535183860904, -0.01172141527276311, -0.0502382139885996, -0.3065609282865831, -0.6116351775358528, -1.1921043554642177 ] for test_value, result_value in zip(f[2:][0], self.spread_roll_tenors_results): self.assertAlmostEquals(test_value, result_value)
def test_buy_protection(self): self.sdate = datetime.datetime(2018, 4, 13) self.value_date = self.sdate.strftime('%d/%m/%Y') # 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) ] self.verbose = 0 f = cds_all_in_one_exclude_ir_tenor_dates( 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.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] 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) # 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
def test_1day_roll_buy_protection_cds_shift(self): """ 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) # self.spread_roll_tenors zero shift self.spread_roll_tenors_results = [-0.00167445351801] for test_value, result_value in zip(f[2:][0], self.spread_roll_tenors_results): self.assertAlmostEquals(f[0][1] - test_value, result_value)
notional = 1.0 is_buy_protection = 0 # only ever buy or sell protection! verbose = 0 tenor_list = [0.5, 1, 2, 3, 4, 5, 7, 10] day_count = 1 one_day = datetime.timedelta(1) spread_roll_tenors = ['1D', '-1D', '-1W', '-1M', '-6M', '-1Y', '-5Y'] scenario_shifts = [-50, -10, 0, 10, 20, 50, 150, 100] for day in range(day_count): # build imm_dates imm_dates = [ f[1] for f in imm_date_vector(start_date=sdate, tenor_list=tenor_list) ] if verbose: print imm_dates # skip any weekend dates if sdate.weekday() in [5, 6]: sdate = sdate + one_day continue value_date = sdate.strftime('%d/%m/%Y') for coupon in coupon_list: f = cds_all_in_one_exclude_ir_tenor_dates( trade_date, effective_date, maturity_date, value_date, accrual_start_date, recovery_rate, coupon, notional,
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) 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.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): """ 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 = [ '1D', '-1D', '-1W', '-1M', '-6M', '-1Y', '-5Y' ] 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) # results to compare against self.spread_roll_tenors_results = { -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 ] } # confirm that we have managed to generate the accurate number of scenario details # self.assertTrue(len(f[2:]), 3) # confirm we have the same dataset for i, a in enumerate(f[2:]): for test_value, result_value in zip( a, self.spread_roll_tenors_results[self.scenario_shifts[i]]): self.assertAlmostEquals(test_value, result_value)