簡體   English   中英

使用Python的QuantLib債券的每日定價

[英]Daily Pricing of a Bond with QuantLib using Python

我想在python中使用QuantLib主要是為了在投資組合環境中為利率工具(跟蹤衍生產品)定價。 主要要求是將日收益率曲線傳遞給系統以連續幾天進行定價(現在暫時忽略系統性能問題)。 我的問題是,是否已正確構造下面的示例來做到這一點? 我的理解是,我每天至少需要一個曲線對象以及必要的鏈接等。我利用熊貓來嘗試這一點。 對此的指導將不勝感激。

import QuantLib as ql
import math
import pandas as pd
import datetime as dt


# MARKET PARAMETRES
calendar = ql.SouthAfrica()
bussiness_convention = ql.Unadjusted
day_count = ql.Actual365Fixed()
interpolation = ql.Linear()
compounding = ql.Compounded
compoundingFrequency = ql.Quarterly


def perdelta(start, end, delta):
    date_list=[]
    curr = start
    while curr < end:
        date_list.append(curr)
        curr += delta
    return date_list


def to_datetime(d):
    return dt.datetime(d.year(),d.month(), d.dayOfMonth())

def format_rate(r):
    return '{0:.4f}'.format(r.rate()*100.00)


#QuantLib must have dates in its date objects
dicPeriod={'DAY':ql.Days,'WEEK':ql.Weeks,'MONTH':ql.Months,'YEAR':ql.Years}


issueDate = ql.Date(19,8,2014)
maturityDate = ql.Date(19,8,2016)

#Bond Schedule
schedule = ql.Schedule (issueDate, maturityDate,
                     ql.Period(ql.Quarterly),ql.TARGET(),ql.Following, ql.Following,
                    ql.DateGeneration.Forward,False)


fixing_days = 0 
face_amount = 100.0


def price_floater(myqlvalDate,jindex,jibarTermStructure,discount_curve):

    bond = ql.FloatingRateBond(settlementDays = 0,
                            faceAmount = 100,
                            schedule = schedule,
                            index = jindex,
                            paymentDayCounter = ql.Actual365Fixed(),
                            spreads=[0.02])   

    bondengine = ql.DiscountingBondEngine(ql.YieldTermStructureHandle(discount_curve))
    bond.setPricingEngine(bondengine)
    ql.Settings.instance().evaluationDate = myqlvalDate
    return [bond.NPV() ,bond.cleanPrice()]


start_date=dt.datetime(2014,8,19)
end_date=dt.datetime(2015,8,19)
all_dates=perdelta(start_date,end_date,dt.timedelta(days=1))
dtes=[];fixings=[]
for d in all_dates:
    if calendar.isBusinessDay(ql.QuantLib.Date(d.day,d.month,d.year)):
        dtes.append(ql.QuantLib.Date(d.day,d.month,d.year))
        fixings.append(0.1)


df_ad=pd.DataFrame(all_dates,columns=['valDate'])
df_ad['qlvalDate']=df_ad.valDate.map(lambda x:ql.DateParser.parseISO(x.strftime('%Y-%m-%d')))
df_ad['jibarTermStructure'] = df_ad.qlvalDate.map(lambda x:ql.RelinkableYieldTermStructureHandle())
df_ad['discountStructure'] = df_ad.qlvalDate.map(lambda x:ql.RelinkableYieldTermStructureHandle())
df_ad['jindex'] = df_ad.jibarTermStructure.map(lambda x: ql.Jibar(ql.Period(3,ql.Months),x))
df_ad.jindex.map(lambda x:x.addFixings(dtes, fixings))
df_ad['flatCurve'] = df_ad.apply(lambda r: ql.FlatForward(r['qlvalDate'],0.1,ql.Actual365Fixed(),compounding,compoundingFrequency),axis=1)
df_ad.apply(lambda x:x['jibarTermStructure'].linkTo(x['flatCurve']),axis=1)
df_ad.apply(lambda x:x['discountStructure'].linkTo(x['flatCurve']),axis=1)
df_ad['discount_curve']= df_ad.apply(lambda x:ql.ZeroSpreadedTermStructure(x['discountStructure'],ql.QuoteHandle(ql.SimpleQuote(math.log(1+0.02)))),axis=1)
df_ad['all_in_price']=df_ad.apply(lambda r:price_floater(r['qlvalDate'],r['jindex'],r['jibarTermStructure'],r['discount_curve'])[0],axis=1)
df_ad['clean_price']=df_ad.apply(lambda r:price_floater(r['qlvalDate'],r['jindex'],r['jibarTermStructure'],r['discount_curve'])[1],axis=1)
df_plt=df_ad[['valDate','all_in_price','clean_price']]
df_plt=df_plt.set_index('valDate')


from matplotlib import ticker

def func(x, pos): 
    s = str(x)
    ind = s.index('.')
    return s[:ind] + '.' + s[ind+1:]  

ax=df_plt.plot()
ax.yaxis.set_major_formatter(ticker.FuncFormatter(func))

多虧了Luigi Ballabio,我重新整理了上面的示例,在QuantLib中納入了設計原理,以避免不必要的調用。 現在靜態數據確實是靜態的,只有市場數據有所變化(我希望)。 我現在更好地了解了活動對象如何偵聽鏈接變量的變化。

靜態數據如下:

  • bondengine
  • structurehandles
  • 歷史吉巴爾指數

市場數據將是唯一變化的組成部分

  • 每日掉期曲線
  • 市場價差於掉期曲線

重做的示例如下:

import QuantLib as ql
import math
import pandas as pd
import datetime as dt
import numpy as np


# MARKET PARAMETRES
calendar = ql.SouthAfrica()
bussiness_convention = ql.Unadjusted
day_count = ql.Actual365Fixed()
interpolation = ql.Linear()
compounding = ql.Compounded
compoundingFrequency = ql.Quarterly


def perdelta(start, end, delta):
    date_list=[]
    curr = start
    while curr < end:
        date_list.append(curr)
        curr += delta
    return date_list


def to_datetime(d):
    return dt.datetime(d.year(),d.month(), d.dayOfMonth())

def format_rate(r):
    return '{0:.4f}'.format(r.rate()*100.00)


#QuantLib must have dates in its date objects
dicPeriod={'DAY':ql.Days,'WEEK':ql.Weeks,'MONTH':ql.Months,'YEAR':ql.Years}


issueDate = ql.Date(19,8,2014)
maturityDate = ql.Date(19,8,2016)

#Bond Schedule
schedule = ql.Schedule (issueDate, maturityDate,
                     ql.Period(ql.Quarterly),ql.TARGET(),ql.Following, ql.Following,
                    ql.DateGeneration.Forward,False)

fixing_days = 0 
face_amount = 100.0

start_date=dt.datetime(2014,8,19)
end_date=dt.datetime(2015,8,19)
all_dates=perdelta(start_date,end_date,dt.timedelta(days=1))
dtes=[];fixings=[]
for d in all_dates:
    if calendar.isBusinessDay(ql.QuantLib.Date(d.day,d.month,d.year)):
        dtes.append(ql.QuantLib.Date(d.day,d.month,d.year))
        fixings.append(0.1)

jibarTermStructure = ql.RelinkableYieldTermStructureHandle()
jindex = ql.Jibar(ql.Period(3,ql.Months), jibarTermStructure)
jindex.addFixings(dtes, fixings)
discountStructure = ql.RelinkableYieldTermStructureHandle()

bond = ql.FloatingRateBond(settlementDays = 0,
                          faceAmount = 100,
                          schedule = schedule,
                          index = jindex,
                          paymentDayCounter = ql.Actual365Fixed(),
                          spreads=[0.02])   

bondengine = ql.DiscountingBondEngine(discountStructure)
bond.setPricingEngine(bondengine)

spread = ql.SimpleQuote(0.0)
discount_curve = ql.ZeroSpreadedTermStructure(jibarTermStructure,ql.QuoteHandle(spread))
discountStructure.linkTo(discount_curve)

# ...here is the pricing function...

# pricing:
def price_floater(myqlvalDate,jibar_curve,credit_spread):
    credit_spread = math.log(1.0+credit_spread)
    ql.Settings.instance().evaluationDate = myqlvalDate
    jibarTermStructure.linkTo(jibar_curve)
    spread.setValue(credit_spread)
    ql.Settings.instance().evaluationDate = myqlvalDate
    return pd.Series({'NPV': bond.NPV(), 'cleanPrice': bond.cleanPrice()})


# ...and here are the remaining varying parts:

df_ad=pd.DataFrame(all_dates,columns=['valDate'])
df_ad['qlvalDate']=df_ad.valDate.map(lambda x:ql.DateParser.parseISO(x.strftime('%Y-%m-%d')))
df_ad['jibar_curve'] = df_ad.apply(lambda r: ql.FlatForward(r['qlvalDate'],0.1,ql.Actual365Fixed(),compounding,compoundingFrequency),axis=1)
df_ad['spread']=np.random.uniform(0.015, 0.025, size=len(df_ad)) # market spread
df_ad['all_in_price'], df_ad["clean_price"]=zip(*df_ad.apply(lambda r:price_floater(r['qlvalDate'],r['jibar_curve'],r['spread']),axis=1).to_records())[1:]


# plot result

df_plt=df_ad[['valDate','all_in_price','clean_price']]
df_plt=df_plt.set_index('valDate')

from matplotlib import ticker


def func(x, pos):  # formatter function takes tick label and tick position
    s = str(x)
    ind = s.index('.')
    return s[:ind] + '.' + s[ind+1:]   # change dot to comma

ax=df_plt.plot()
ax.yaxis.set_major_formatter(ticker.FuncFormatter(func))

您的解決方案可以使用,但是每天創建一個綁定有點違反庫的范圍。 您可以只創建一次債券和JIBAR指數,只需更改評估日期和相應的曲線即可。 綁定將檢測到更改並重新計算。

在一般情況下,這類似於:

# here are the parts that stay the same...

jibarTermStructure = ql.RelinkableYieldTermStructureHandle()
jindex = ql.Jibar(ql.Period(3,ql.Months), jibarTermStructure)
jindex.addFixings(dtes, fixings)
discountStructure = ql.RelinkableYieldTermStructureHandle()

bond = ql.FloatingRateBond(settlementDays = 0,
                          faceAmount = 100,
                          schedule = schedule,
                          index = jindex,
                          paymentDayCounter = ql.Actual365Fixed(),
                          spreads=[0.02])   

bondengine = ql.DiscountingBondEngine(discountStructure)
bond.setPricingEngine(bondengine)

# ...here is the pricing function...

def price_floater(myqlvalDate,jibar_curve,discount_curve):
    ql.Settings.instance().evaluationDate = myqlvalDate
    jibarTermStructure.linkTo(jibar_curve)
    discountStructure.linkTo(discount_curve)
    return [bond.NPV() ,bond.cleanPrice()]

# ...and here are the remaining varying parts:

df_ad=pd.DataFrame(all_dates,columns=['valDate'])
df_ad['qlvalDate']=df_ad.valDate.map(lambda x:ql.DateParser.parseISO(x.strftime('%Y-%m-%d')))
df_ad['flatCurve'] = df_ad.apply(lambda r: ql.FlatForward(r['qlvalDate'],0.1,ql.Actual365Fixed(),compounding,compoundingFrequency),axis=1)
df_ad['discount_curve']= df_ad.apply(lambda x:ql.ZeroSpreadedTermStructure(jibarTermStructure,ql.QuoteHandle(ql.SimpleQuote(math.log(1+0.02)))),axis=1)
df_ad['all_in_price']=df_ad.apply(lambda r:price_floater(r['qlvalDate'],r['flatCurve'],r['discount_curve'])[0],axis=1)
df_ad['clean_price']=df_ad.apply(lambda r:price_floater(r['qlvalDate'],r['flatCurve'],r['discount_curve'])[0],axis=1)
df_plt=df_ad[['valDate','all_in_price','clean_price']]
df_plt=df_plt.set_index('valDate')

現在,即使在最一般的情況下,上述條件也可以優化:每個日期兩次調用price_floater ,因此您需要做兩次工作。 我對熊貓不熟悉,但是我猜您可以打一個電話,並通過一次分配即可設置df_ad['all_in_price']df_ad['clean_price']

而且,根據您的用例,可能還有進一步簡化代碼的方法。 折扣曲線可能會被實例化一次,而點差在定價期間會發生變化:

# in the "only once" part:
spread = ql.SimpleQuote()
discount_curve = ql.ZeroSpreadedTermStructure(jibarTermStructure,ql.QuoteHandle(spread))
discountStructure.linkTo(discount_curve)

# pricing:
def price_floater(myqlvalDate,jibar_curve,credit_spread):
    ql.Settings.instance().evaluationDate = myqlvalDate
    jibarTermStructure.linkTo(jibar_curve)
    spread.setValue(credit_spread)
    return [bond.NPV() ,bond.cleanPrice()]

在不同的部分,您將只有一系列的信用利差與一系列的折扣曲線。

如果曲線都是平坦的,則可以利用另一個功能進行相同的操作:如果使用天數和日歷而不是日期來初始化曲線,則其參考日期將隨評估日期一起移動(如果數字為的天數是0,將是評估日期;如果是1,則將是下一個工作日,依此類推。

# only once:
risk_free = ql.SimpleQuote()
jibar_curve = ql.FlatForward(0,calendar,ql.QuoteHandle(risk_free),ql.Actual365Fixed(),compounding,compoundingFrequency)
jibarTermStructure.linkTo(jibar_curve)

# pricing:
def price_floater(myqlvalDate,risk_free_rate,credit_spread):
    ql.Settings.instance().evaluationDate = myqlvalDate
    risk_free.linkTo(risk_free_rate)
    spread.setValue(credit_spread)
    return [bond.NPV() ,bond.cleanPrice()]

在不同的部分,您將使用簡單的比率陣列替換吉巴曲線陣列。

上面的代碼應該為您提供與代碼相同的結果,但是將實例化更少的對象,從而可能節省內存並提高性能。

最后的警告:如果熊貓的map並行評估結果,我的代碼和您的代碼都不起作用; 您最終將嘗試同時將全局評估日期設置為多個值,但效果並不理想。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM