Vanilla Interest Rate Swap Valuation in Python using QuantLib - python-3.x

I am valuing a Vanilla Interest Rate Swap as at 31 January 2017 (Valuation Date) but the effective date of the Vanilla Interest Rate Swap is 31 December 2016 (Start Date). Firstly, I would like to know how I can adjust for my valuation date and start date in the code below;
import QuantLib as ql
startDate = ql.Date(31,12,2016)
valuationDate = ql.Date(31,1,2017)
maturityDate = ql.Date(30,9,2019)
calendar = ql.SouthAfrica()
bussiness_convention = ql.Unadjusted
swapT = ql.VanillaSwap.Payer
nominal = 144000000
fixedLegTenor = ql.Period(3, ql.Months)
fixedSchedule = ql.Schedule(startDate, maturityDate,
fixedLegTenor, calendar,
ql.ModifiedFollowing, ql.ModifiedFollowing,
ql.DateGeneration.Forward, False)
fixedRate = 0.077
fixedLegDayCount = ql.Actual365Fixed()
spread = 0
floatLegTenor = ql.Period(3, ql.Months)
floatSchedule = ql.Schedule(startDate, maturityDate,
floatLegTenor, calendar,
ql.ModifiedFollowing, ql.ModifiedFollowing,
ql.DateGeneration.Forward, False)
floatLegDayCount = ql.Actual365Fixed()
discountDates = [ql.Date(31,1,2017),ql.Date(7,2,2017),ql.Date(28,2,2017),
ql.Date(31,3,2017),ql.Date(28,4,2017),ql.Date(31,7,2017),
ql.Date(31,10,2017),ql.Date(31,1,2018),ql.Date(31,1,2019),
ql.Date(31,1,2020),ql.Date(29,1,2021),ql.Date(31,1,2022),
ql.Date(31,1,2023),ql.Date(31,1,2024),ql.Date(31,1,2025),
ql.Date(30,1,2026),ql.Date(29,1,2027),ql.Date(31,1,2029),
ql.Date(30,1,2032),ql.Date(30,1,2037),ql.Date(31,1,2042),
ql.Date(31,1,2047)]
discountRates = [1,0.9986796,0.99457,0.9884423,0.9827433,0.9620352,0.9420467,0.9218714,
0.863127,0.7993626,0.7384982,0.6796581,0.6244735,0.5722537,0.5236629,
0.4779477,0.4362076,0.3619845,0.2795902,0.1886847,0.1352048,0.1062697]
jibarTermStructure = ql.RelinkableYieldTermStructureHandle()
jibarIndex = ql.Jibar(ql.Period(3,ql.Months), jibarTermStructure)
jibarIndex.addFixings(dtes, fixings)
discountCurve = ql.DiscountCurve(discountDates,discountRates,ql.Actual365Fixed())
swapEngine = ql.DiscountingSwapEngine(discountCurve)
interestRateSwap = ql.VanillaSwap(swapT, nominal, fixedSchedule,
fixedRate,fixedLegDayCount,jibarIndex,spread,floatSchedule,
floatLegDayCount,swapEngine,floatSchedule.convention)
Secondly, I would like to know how best I can incorporate jibarIndex in interestRateSwap =ql.VanillaSwap() or else how can I use my Discount Factors and Discount Dates to calculate the value of the Interest Rate Swap

ql.Settings.instance().setEvaluationDate(today)
sets the evaluation date or valuation date.
I don't see a problem with your jibarIndex index in the VanillaSwap code. You've given the object to QuantLib, the library will use it as a forward curve to price your floating leg and thus the swap.

Related

Compare QuantLib bond pricing with Excel functions YIELD and PRICE when doing stress testing

I calculated bond price and stressed bond price (shocking up yield) in both Excel and Python Quantlib. As the following table shows, weird results were generated: base bond price matches well between Excel and Quantlib but the stressed bond prices have more gaps (1.5% relative difference). Yields also show some gaps.
Can you please provide some comments?
Excel Code:
Base Bond Price=PRICE("2021-8-19","2025-8-19",4.5%,YIELD("2021-8-19","2025-8-
19",4.5%,95,100,4,0),100,4,0)
Stress Bond Price=PRICE("2021-8-19","2025-8-19",4.5%,YIELD("2021-8-19","2025-8-
19",4.5%,95,100,4,0)+662/10000,100,4,0)
Python code:
import datetime
import QuantLib as ql
settlement_date = ql.Date(19,8,2021)
valuation_date = ql.Date(19,8,2021)
issue_date = ql.Date(19,8,2021)
maturity_date = ql.Date(19,8,2025)
tenor = ql.Period(4)
calendar = ql.UnitedStates()
business_convention = ql.Following
date_generation = ql.DateGeneration.Backward
end_month = False
face_value = 100
coupon_rate = 450/10000
day_count = ql.Thirty360(ql.Thirty360.USA)
redemption_value = 100
schedule = ql.Schedule(issue_date, maturity_date, tenor, calendar, business_convention, business_convention, date_generation, end_month)
bond = ql.FixedRateBond(settlement_date-valuation_date, face_value, schedule, [coupon_rate], day_count, business_convention, redemption_value, issue_date)
target_price = 95
bond_yield = bond.bondYield(target_price, day_count, ql.Compounded, 4, ql.Date(), 1.0e-8,1000)
bond_price = bond.cleanPrice(bond_yield, day_count, ql.Compounded, 4)
STRESS = 662
stress_bond_yield = bond_yield+STRESS/10000
stress_bond_price = bond.cleanPrice(stress_bond_yield, day_count, ql.Compounded, 4)
excel_base_bond_price = 99.50
excel_stress_bond_price = 75.02971569
print('Base bond price from excel is', excel_base_bond_price )
print('Base bond price from Quantlib is', bond_price)
print('Stressed bond price from excel is',excel_stress_bond_price)
print('Stressed bond price from Quantlib is',stress_bond_price)
You need to add
ql.Settings.instance().evaluationDate = valuation_date
before the calculation. If you don't do so, you'll be calculating the yield and price as of today instead of as of the valuation date. Once you do that, the yields and prices match a lot better.

Avoiding cartesian when adding unique classifier to a list in python 3

I have 5 .csv files I am importing and all contain emails:
Donors = pd.read_csv(r"C:\Users\am\Desktop\email parsing\Q1 2021\Donors Q1 2021 R12.csv",
usecols=["Email Address"])
Activists = pd.read_csv(r"C:\Users\am\Desktop\email parsing\Q1 2021\Activists Q1 2021 R12.csv",
usecols=["Email"])
Low_Level_Activists = pd.read_csv(r"C:\Users\am\Desktop\email parsing\Q1 2021\Low Level Activists Q1 2021 R12.csv",
usecols=["Email"])
Ambassadors = pd.read_csv(r"C:\Users\am\Desktop\email parsing\Q1 2021\Ambassadors Q1 2021.csv",
usecols=["Email Address"])
Volunteers = pd.read_csv(r"C:\Users\am\Desktop\email parsing\Q1 2021\Volunteers Q1 2021 R12.csv",
usecols=["Email Address"])
Followers= pd.read_csv(r"C:\Users\am\Desktop\email parsing\Q1 2021\Followers Q1 2021 R12.csv",
usecols=["Email"])
While I am only importing emails (annoyingly with two different naming conventions because of the systems they originate from), I am adding the import name as a classifer - i.e. Donors, Volunteers, etc.
Donors['Value'] = "Donors"
Activists['Value'] = "Activists"
Low_Level_Activists['Value'] = "Low_Level_Activists"
Ambassadors['Value'] = "Ambassadors"
Volunteers['Value'] = "Volunteers"
Advocates['Value'] = 'Followers'
I then concatenate all the files and handle the naming issue. I am sure there is a more elegant way to do this but here's what I have:
S1= pd.concat([Donors,Activists,Low_Level_Activists,Ambassadors,Volunteers,Advocates], ignore_index= True)
S1['Handle'] = S1['Email Address'].where(S1['Email Address'].notnull(), S1['Email'])
S1= S1.drop(['Email','Email Address'], axis = 1)
print(S1['Handle'].count()) #checks full count
The total on that last line is 166,749
Here is my problem. I need to filter the emails for uniques - easy enough using .nuniques() and the but the problem I am having is I also need to carry the classifier. So if a singular email is a Donor but also an Activist, I pull both when I try to merge the unique values with the classifier.
I have been at this for many hours (and to the end of the Internet!) and can't seem to find a workable solution. I've tried dictionary for loops, merges, etc. ad infinitum. The unique email count is 165,923 (figured out via Python &/or excel:( ).
Essentially I would want to pull the earliest classifier in my list on a match. So if an email is a Donor and an Activist-> call them a Donor. Or if a email is a Volunteer and a Follower -> call them a Volunteer on one email record.
Any help would be greatly appreciated.
I'll give it a try with some made-up data:
import pandas as pd
fa = pd.DataFrame([['paul#mail.com', 'Donors'], ['max#mail.com', 'Donors']], columns=['Handle', 'Value'])
fb = pd.DataFrame([['paul#mail.com', 'Activists'], ['annie#mail.com', 'Activists']], columns=['Handle', 'Value'])
S1 = pd.concat([fa, fb])
print(S1)
gives
Handle Value
0 paul#mail.com Donors
1 max#mail.com Donors
0 paul#mail.com Activists
1 annie#mail.com Activists
You can group by Handle and then pick any Value you like, e.g. the first:
for handle, group in S1.groupby('Handle'):
print(handle, group.reset_index().loc[0, 'Value'])
gives
annie#mail.com Activists
max#mail.com Donors
paul#mail.com Donors
or collect all roles of a person:
for handle, group in S1.groupby('Handle'):
print(handle, group.Value.unique())
gives
annie#mail.com ['Activists']
max#mail.com ['Donors']
paul#mail.com ['Donors' 'Activists']

Python 3 formatting issue with '$'

I just started learning Python this week and I'm having an issue with some line formatting. I am trying to insert a '$' along with the calculated values, I've tried to insert the '$' with a new {} set, as well as here {{$:>{}.2f}} (based on Google search suggestions), however I keep getting errors.
I'm using Google Collab to run the code.
def budget_calc(money, rent, food, phone, gas, extra_income):
width = 35
price_width = 10
item_width = width - price_width
income = sum(weekly_income) + extra_income
expenses = sum(credit_cards) + rent + phone + food + gas + utilities
after_tax =income*0.84
#after_tax_instr = str(after_tax)
gross = 'Money before tax:' #+ str(income)
post_tax= 'Post tax income:' #+after_tax_instr
tax_payment ='Taxes due:' #+str(income*.16)
savings = after_tax - expenses
total = 'Money left over:' #+'$'+str(savings)
expense_total = 'Total expenses' #+str([expenses])
line = "{{:{}}} {{:>{}.2f}}".format(item_width, price_width)
print(line.format(gross, income))
print(line.format(tax_payment, (income*0.16)))
print(line.format(post_tax, after_tax))
print(line.format(expense_total, expenses))
print(line.format(total, savings))
The output I get is:
Money before tax: 3300.00
Taxes due: 528.00
Post tax income: 2772.00
Total expenses 2190.00
Money left over: 582.00
Days until my Mac: 44.36
I would appreciate any pointers in the right direction.
I think the error it throws will be keyerror and it because of the messy string format
def budget_calc(money, rent, food, phone, gas, extra_income):
width = 35
price_width = 10
item_width = width - price_width
income = sum(weekly_income) + extra_income
expenses = sum(credit_cards) + rent + phone + food + gas + utilities
after_tax =income*0.84
#after_tax_instr = str(after_tax)
gross = 'Money before tax:' #+ str(income)
post_tax= 'Post tax income:' #+after_tax_instr
tax_payment ='Taxes due:' #+str(income*.16)
savings = after_tax - expenses
total = 'Money left over:' #+'$'+str(savings)
expense_total = 'Total expenses' #+str([expenses])
# You can specify the width integer instead of formating
line = "{0:<25} ${1:.2f}"
print(line.format(gross, income))
print(line.format(tax_payment, (income*0.16)))
print(line.format(post_tax, after_tax))
print(line.format(expense_total, expenses))
print(line.format(total, savings))
Note : since weekly_income and credit_cards are not provided i just
removed it from script in order to check the output
When `budget_calc(33000,500,10,100,152,510)` this is called the output
Output :
Money before tax: $33510.00
Taxes due: $5361.60
Post tax income: $28148.40
Total expenses $762.00
Money left over: $27386.40
Python 3.6+ String interpolation is recommended way of string formatting because of its high readabililty.
The best way would be:
print(f'Money before tax: {income}')
print(f'Taxes due:{income*0.16}')
print(f'Post tax income: {after_tax}')
print(f'Total expenses: {expenses}')
print(f'Money left over :{savings}')
But if you want to use $ String Formatting only , the you should try the answer by DaVinci

Parsing heterogenous data from a text file in Python

I am trying to parse raw data results from a text file into an organised tuple but having trouble getting it right.
My raw data from the textfile looks something like this:
Episode Cumulative Results
EpisodeXD0281119
Date collected21/10/2019
Time collected10:00
Real time PCR for M. tuberculosis (Xpert MTB/Rif Ultra):
PCR result Mycobacterium tuberculosis complex NOT detected
Bacterial Culture:
Bottle: Type FAN Aerobic Plus
Result No growth after 5 days
EpisodeST32423457
Date collected23/02/2019
Time collected09:00
Gram Stain:
Neutrophils Occasional
Gram positive bacilli Moderate (2+)
Gram negative bacilli Numerous (3+)
Gram negative cocci Moderate (2+)
EpisodeST23423457
Date collected23/02/2019
Time collected09:00
Bacterial Culture:
A heavy growth of
1) Klebsiella pneumoniae subsp pneumoniae (KLEPP)
ensure that this organism does not spread in the ward/unit.
A heavy growth of
2) Enterococcus species (ENCSP)
Antibiotic/Culture KLEPP ENCSP
Trimethoprim-sulfam R
Ampicillin / Amoxic R S
Amoxicillin-clavula R
Ciprofloxacin R
Cefuroxime (Parente R
Cefuroxime (Oral) R
Cefotaxime / Ceftri R
Ceftazidime R
Cefepime R
Gentamicin S
Piperacillin/tazoba R
Ertapenem R
Imipenem S
Meropenem R
S - Sensitive ; I - Intermediate ; R - Resistant ; SDD - Sensitive Dose Dependant
Comment for organism KLEPP:
** Please note: this is a carbapenem-RESISTANT organism. Although some
carbapenems may appear susceptible in vitro, these agents should NOT be used as
MONOTHERAPY in the treatment of this patient. **
Please isolate this patient and practice strict contact precautions. Please
inform Infection Prevention and Control as contact screening might be
indicated.
For further advice on the treatment of this isolate, please contact.
The currently available laboratory methods for performing colistin
susceptibility results are unreliable and may not predict clinical outcome.
Based on published data and clinical experience, colistin is a suitable
therapeutic alternative for carbapenem resistant Acinetobacter spp, as well as
carbapenem resistant Enterobacteriaceae. If colistin is clinically indicated,
please carefully assess clinical response.
EpisodeST234234057
Date collected23/02/2019
Time collected09:00
Authorised by xxxx on 27/02/2019 at 10:35
MIC by E-test:
Organism Klebsiella pneumoniae (KLEPN)
Antibiotic Meropenem
MIC corrected 4 ug/mL
MIC interpretation Resistant
Antibiotic Imipenem
MIC corrected 1 ug/mL
MIC interpretation Sensitive
Antibiotic Ertapenem
MIC corrected 2 ug/mL
MIC interpretation Resistant
EpisodeST23423493
Date collected18/02/2019
Time collected03:15
Potassium 4.4 mmol/L 3.5 - 5.1
EpisodeST45445293
Date collected18/02/2019
Time collected03:15
Creatinine 32 L umol/L 49 - 90
eGFR (MDRD formula) >60 mL/min/1.73 m2
Creatinine 28 L umol/L 49 - 90
eGFR (MDRD formula) >60 mL/min/1.73 m2
Essentially the pattern is that ALL information starts with a unique EPISODE NUMBER and follows with a DATE and TIME and then the result of whatever test. This is the pattern throughout.
What I am trying to parse into my tuple is the date, time, name of the test and the result - whatever it might be. I have the following code:
with open(filename) as f:
data = f.read()
data = data.splitlines()
DS = namedtuple('DS', 'date time name value')
parsed = list()
idx_date = [i for i, r in enumerate(data) if r.strip().startswith('Date')]
for start, stop in zip(idx_date[:-1], idx_date[1:]):
chunk = data[start:stop]
date = time = name = value = None
for row in chunk:
if not row: continue
row = row.strip()
if row.startswith('Episode'): continue
if row.startswith('Date'):
_, date = row.split()
date = date.replace('collected', '')
elif row.startswith('Time'):
_, time = row.split()
time = time.replace('collected', '')
else:
name, value, *_ = row.split()
print (name)
parsed.append(DS(date, time, name, value))
print(parsed)
My error is that I am unable to find a way to parse the heterogeneity of the test RESULT in a way that I can use later, for example for the tuple DS ('DS', 'date time name value'):
DATE = 21/10/2019
TIME = 10:00
NAME = Real time PCR for M tuberculosis or Potassium
RESULT = Negative or 4.7
Any advice appreciated. I have hit a brick wall.

Why is time conversion between epoch seconds and string changing time by 1 calendar year?

I am using the time module of python3 to convert time between seconds and formatted string. Python functions used to generate string are localtime and strftime. To generate the time in seconds, I use string splicing followed by mktime. As I call these repeatedly on each result, only the year changes, always incrementing the seconds by a full year.
Code used is as below:
import time
def time_string(t):
#t is second obtained by time.mktime((yr, mn, dy, hr, mn, sec, 0, 0, 0))
time_struct = time.localtime(t)
time_string = time.strftime("%Y-%m-%d %H:%M:%S", time_struct)
return time_string
def string_time(t_string):
#t_string has format '2020-01-31 08:23:35'
yr = int(t_string[:4])
mn = int(t_string[5:7])
dy = int(t_string[8:10])
hr = int(t_string[11:13])
mn = int(t_string[14:16])
se = int(t_string[17:])
t=int(time.mktime((yr, mn, dy, hr, mn, se, 0, 0, 0)))
return t
t = int(time.mktime((2020, 3, 19, 18, 15, 20, 0, 0, 0)))
print (t)
for x in range(5):
t_st = time_string(t)
print(t_st)
t = string_time(t_st)
print(t)
sys.exit("stopping..")
The results I get from above code execution is as follows:
1584621920
2020-03-19 18:15:20
1616157920
2021-03-19 18:15:20
1647693920
2022-03-19 18:15:20
1679229920
2023-03-19 18:15:20
1710852320
2024-03-19 18:15:20
1742388320
SystemExit: stopping..
What am I doing wrong? Why does this happen?
What is a better way of converting time-string to seconds?
I do not get the purpose of the question, so what you're actually trying to do, however if you have a string of a time, and you want to have the seconds of it, try using datetime.timestamp() instead of a time-string-splicing...
Your code is increasing in the year by one beacuse in your method string_time(t_string) you set the variable mn twice! One time at mn = int(t_string[5:7]) and once at mn = int(t_string[14:16]) which will result in a month of 15 which will adapt the year by 1 year and 3 month which will result in the one year for you
Found time.strptime to solve the problem of converting back from string using the right formatters. The following code eliminated the need to do string splicing
def string_time(t_string):
#t_string has format '2020-01-31 08:23:35'
t_struct = time.strptime(t_string,"%Y-%m-%d %H:%M:%S")
t = int(time.mktime(t_struct))
return t
Korbinian had already found the error in my code. Is there a reason why I should use the datetime module instead of the date module?

Resources