1

我想在 python 中使用 QuantLib 主要为投资组合环境中的利率工具(衍生品)定价。主要要求是将每日收益率曲线传递给系统以连续几天定价(让我们暂时忽略系统性能问题)。我的问题是,我是否正确构建了下面的示例来做到这一点?我的理解是,我每天至少需要一个带有必要链接等的曲线对象。我已经使用 pandas 来尝试这个。对此的指导将不胜感激。

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,以避免不必要的调用。现在静态数据是真正的静态数据,只有市场数据变化(我希望)。我现在更好地理解了活动对象如何监听链接变量的变化。

静态数据如下:

  • 债券引擎
  • 结构句柄
  • 历史吉巴尔指数

市场数据将是唯一变化的组成部分

  • 每日掉期曲线
  • 互换曲线上的市场价差

修改后的示例如下:

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))
4

1 回答 1

4

您的解决方案会奏效,但每天创建一个债券有点违背图书馆的宗旨。您只需创建一次债券和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()]

在不同的部分中,您将只有一系列信用利差,而不是一系列贴现曲线。

如果曲线都是平坦的,你可以利用另一个特性来做同样的事情:如果你用天数和日历而不是日期来初始化曲线,它的参考日期将随着评估日期而移动(如果数字of days 为 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()]

在不同的部分,您将用一个简单的速率数组替换 jibar 曲线数组。

以上应该给您与您的代码相同的结果,但会实例化更少的对象,因此可能会节省内存并提高性能。

map最后一个警告:如果 pandas并行评估结果,我和你的代码都不会起作用;您最终会尝试同时将全局评估日期设置为多个值,这不会很顺利。

于 2015-10-01T21:06:54.947 回答