QuantLib实现.docx

上传人:b****6 文档编号:7836003 上传时间:2023-01-26 格式:DOCX 页数:18 大小:181.10KB
下载 相关 举报
QuantLib实现.docx_第1页
第1页 / 共18页
QuantLib实现.docx_第2页
第2页 / 共18页
QuantLib实现.docx_第3页
第3页 / 共18页
QuantLib实现.docx_第4页
第4页 / 共18页
QuantLib实现.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

QuantLib实现.docx

《QuantLib实现.docx》由会员分享,可在线阅读,更多相关《QuantLib实现.docx(18页珍藏版)》请在冰豆网上搜索。

QuantLib实现.docx

QuantLib实现

 

QuantLib实现

LuigiBallabio编

©2005,2006,2007,2008LuigiBallabio.

本书遵守知识共享(CreativeCommonsAttribution)许可协议3.0版,禁止用于商业用途。

查看该许可协议请访问:

http:

//creativecommons.org/licenses/by-nc-nd/3.0/

或者发送信函到知识共享组织(CreativeCommons),559NathanAbbottWay,Stanford,California94305,USA.

作者对于编写本书做了认真准备,但并意味或暗示任何类型的担保,同时也不为本书的错误或疏漏承担任何责任。

作者并不保证对于使用本书中的信息或者程序是否会造成的偶然或相应的损失。

署名-非商业使用-禁止演绎3.0Unported

您可以自由:

复制、发行、展览、表演、放映、广播或通过信息网络传播本作品

惟须遵守以下条款:

署名:

您必须按照作者或者许可人指定的方式对作品进行署名

非商业性使用:

您不得将本作品用于商业目的

禁止演绎.您不得修改、转换或者以本作品为基础进行创作

•对任何再使用或者发行,您都必须向他人清楚地展示本作品使用的许可协议条款。

http:

//creativecommons.org/licenses/by-nc-nd/3.0/

•如果得到著作权人的许可,您可以不受任何这些条件的限制。

•Nothinginthislicenseimpairsorrestrictstheauthor’smoralrights.

Yourfairdealingandotherrightsareinnowayaffectedbytheabove.

Thisisahuman-readablesummaryoftheLegalCode(thefulllicense).

TheLegalCodeisavailableat

http:

//creativecommons.org/licenses/by-nc-nd/3.0/legalcode.

目录

1引论1

2金融产品与定价引擎3

2.1工具类…………………………………………………………………………………….3

2.2定价引擎………………………………………………………………………………….9

引论

原因与理由

以年轻人的热情,QuantLib网站常常阐明要把QuantLib打造成“标准、共享、开源的金融工程库”。

通过把这个观点解释的稍微轻松些,有人或许认为它已经成功了,而且长时间以来它也是唯一的开源金融工程库。

无论标准与否,该项目正蓬勃发展;写本书的同时,每个新版本都有数千次的下载,也有来自用户稳定的贡献,该库似乎也已经用于实际中-尽管我猜测这种应用于实际金融中的方式可能比较隐蔽。

总的说来,作为该项目的管理员,我能宣称自己是一个高兴的集成者。

但我们也应该注意到缺少必要文档的弊端正在显现。

尽管有详细的类图(可以自动生成,所以比较容易),但并不能让人从整体上进行理解。

因此一个新手也许会有这样的印象:

QuantLib开发者分享了LewisCarrol的作品《斯纳克之猎》中Bellman的观点:

“WhatuseareMercator’sNorthPolesandEquators,Tropics,ZonesandMeridianLines?

SotheBellmanwouldcry:

andthecrewwouldreply,“Theyaremerelyconventionalsigns!

本书的目的在于填补一些实际中的空白,它是关于QuantLib设计与实现的报告,如果你是或者想成为QuantLib库的使用者,你能从库的设计中看到很多有用的信息,不过当阅读代码时不一定能很容易看到这一点。

如果你在数量金融领域工作,即使不用QuantLib库,通过阅读库的设计报告,你会从中看到许多你工作中遇到的问题,或者从设计中你可能会找到一些合理的解决方案,如果由于你所处环境而选择了其他方案,通过方案的对比也能从中受益。

在本书中,我也会指出当前设计中的一些缺点,不过这样做是为了更多有用的目的但这并不代表我会诋毁QuantLib(毕竟我已经深深迷恋其中)。

同时,描述现有的一些缺陷也能够帮助开发者们避免这些缺陷。

另外,我也许也会提及一些改善该库的方法,过去我常常通过不断阅读本书的代码来更好的完善它。

由于空间与时间的原因,本书并不能包含QuantLib的每个方面。

在该书的前半部分,我将描述一些重要的类,比如那些金融产品模型和术语结构,这将帮助你理解本库的体系结构。

在第二部分,我将描述一些特别的框架,比如蒙特卡罗或有限差分模型,这些模型比其他的更为有用。

我希望QuantLib库的一些缺点能和其优点一样令人产生兴趣。

本书主要面向的是那些用自己的金融产品或模型对本库进行扩展的用户,如果你想这么做,本书中关于类结构和模型框架的描述将提供给你一些有用信息帮助你把自己的代码集成到QuantLib中并使用其中它的一些工具,如果你不是这种类型的用户,也不要合上本书,你同样能找到有用的信息。

不管怎样,我在这里做个暗示:

另外一名QuantLib管理员多次表达了想写一本《QuantLib使用》的书,希望他能够尽快完成此书。

作为传统,关于本书的一些风格和条件在下面将做一下说明。

具有C++和数量金融知识,本书已经足够厚了,因此本书不能对其中的任何一项进行讲解。

在这里,我仅仅描述QuantLib的设计和实现,把上述问题及编程语法和技巧留给更好的作者去完成。

可能你已经注意到了,在本书中我是以第一人称进行写作。

实际上,这看起来有些自我,不过我希望这样做可以使内容不至于过于混乱,不过如果使用多人写作的话又让人感觉有些华而不实。

同时我也感觉用第三人称也会存在同样的问题,三思而后,我倾向于用非正式的但更为舒适的风格进行写作。

同理,在本书中使用你代替所有的读者,这也将有益于避免混乱。

当我用复数的时候,也意味着QuantLib是由所有开发者组成的小组完成的。

我将基于有趣的设计本身描述设计演化,或者与最终结果相关。

为了清晰,大多数情况下我将略过设计中遇到的一些错误的决定,而是把不同时期的设计集中在一起,仅仅讨论最终的设计,有时候这样更容易些。

我将指出代码中使用到的设计模式。

注意,我并不提倡用设计模式任意的在你自己的代码中使用。

他们仅仅是在应该用的时候使用。

然而,QuantLib是多年编码和重构的结果,自然而然设计是朝着模式的方向演变。

本书中我使用的代码列表也遵守代码中使用的规范,附录B中对这些规范进行了概括。

由于行长的限制,我把命名空间的std和boost从类型名称中去掉。

当代码列表需要图进行补充时,我使用UML方便那些对C++不熟悉的用户理解,附录C是一个简单的例子介绍。

好了,就这么多了,让我们开始吧。

金融产品与定价引擎

条条大路通罗马

金融工程库必须能够提供各种金融产品的定价方法,这点看起来很吸引人,但这也只是整个问题的一部分。

金融工程库也必须能够提供开发者通过增加新的定价功能来扩展它的方法。

金融工程库需要满足上述扩展的任何一个,一方面它能够允许增加新的金融产品,另一方面也能够很容易的对已有金融产品增加新的定价方法,这两种需要迫使其解决方案必须相一致。

本章详细描述了QuantLib满足这些要求的方案。

2.1.Instrument类

在编程的领域内,金融产品有它自己的概念。

但从这点而言,任何喜欢面向对象程序员都会通过一个基类来派生每种具体的金融产品。

QuantLib中有这样一个类也是自然而然的,这样做也已被证明有许多好处,最明显的一个就是用户可以统一管理各种金融产品,实际的金融工程中也是如此。

例如,有人对由一系列金融工具组成的投资组合的总值感兴趣,通常的做法是论询该投资组合并获取每一类的值;C++中,立即可以想到的是通过基类指针容器来存储金融产品并依次调用其通用接口来完成。

2.1.1.接口与需求

首先我们看看命名为Instrument类的详细设计,第一个问题是找到该类的公共接口,也即包含所有的金融产品公共方法。

不过,由于金融产品的范畴过于宽泛,从最简单的到最复杂的,这也潜在的表明了,对于一类金融产品(比如,股票期权)是合理的接口设计并一定适用于其他类的金融产品(比如,利率互换)。

这样的话,能作为公共接口的方法就比较少了,我们的设计中把两类方法作为公共接口,一类是计算金融工具的现值(可能与错误定价相关),另一类是该工具是否过期。

接口设计如列表2.1所示。

表2.1:

Instrument类原始接口

classInstrument{

public:

virtual~Instrument();

virtualRealNPV()const=0;

virtualRealerrorEstimate()const=0;

virtualboolisExpired()const=0;

};

作为好的实践,所有方法设计为纯虚方法,不过就像Gershwin指出的也不一定非要如此;有一些行为方法需要在基类中编码,为了找出这些方法,我们必须分析一下通过的金融产品并找出哪些方法是通用的。

当然这两个需求随时间而不同,并且随着库的开发也会发生变化,我把当前的设计在这里说明一下。

一类特定的金融产品也许会用不同的方法进行定价(一个或多个分析公式或者数值方法),但并不需要通过继承来实现,对于这一点熟悉设计模式的读者可能立即想到了策略模式,在这里也的确如此,我将在2.2节进行说明。

第二点需求主要源于金融产品的价值受市场数据的影响,市场数据随时间变化导致金融产品的价值也随之变化,另一个导致变化的理由是单一市场数据的数据源可能有多个,我们需要金融产品能够访问到所有这些数据源,通过这些数据源能够访问到所有最近的值并相应的重新计算结果。

同时我们也能够很方便在不同的数据源间切换,金融产品也可以把这个作为值变化的一种。

然后,这也许会使效率方面有所损失。

例如,我们通过存储在容器中的金融产品监控投资组合的值,周期的轮询这些金融产品的值,并计算结果。

在粗略的实现中,这也将导致那些输入并没有变化的金融产品也重新计算。

因此,我们为金融方法增加一个缓冲机制,将先前计算的结果进行保存,只有有变化时才重新计算。

2.1.2.实现

管理缓冲和重计算的代码通过两个设计模式被写成通用方法。

当任何输入产生变化时,通过观察者模式通知,附录A简单描述了观察者模式,这里我描述一下参与者。

很显然,金融产品扮演观察者的角色而输入数据扮演被观察角色,当数据变化被通知时观察者需要维护一个代表输入的引用,这可以通过智能指针的方式来完成。

然后指针的行为并不足以描述我们的问题。

就像前述提到的,变化并不仅仅来自于数据输入,同时数据输入方式也可能产生变化。

通过智能指针我们可以访问指针对象的当前值,但指针的拷贝对观察者而言是私有的,并不能指向一个不同的对象。

因此我们需要一个指向指针的指针的智能替代,QuantLib通过类模板实现这个特征,其名称为Handle,附录A给出了详细细节。

与本讨论相关的一个事实是给定的Handle有多个拷贝连接于一个对象,当这个连接指向其他对象时,所有拷贝被通知并允许持有者去访问新的指针。

另外,Handle也转发来自观察者的指针对象的变化。

最后,充当被观察者的类能被存到Handles中。

最基本的是Quote类,代表单一变化市场数据,其他金融产品输入可能包括更复杂的对象比如收益率或波动类期限结构。

另外一个问题是把存储和重计算缓冲机制的代码通过抽象类实现,具体的计算通过继承该类来完成。

这些可以通过各种各样的模板方法模式实现,在早期版本的QuantLib中,该功能被包含在Instrument类中,后来该类被抽象并写入另外一个类中,其名字多少有点怪异称为LazyObject,该类在该库的其他部分也被使用到,表2.2概括了该类的信息。

表2.1类LazyObject描述

classLazyObject:

publicvirtualObserver,

publicvirtualObservable{

protected:

mutableboolcalculated_;

virtualvoidperformCalculations()const=0;

public:

voidupdate(){calculated_=false;}

virtualvoidcalculate()const{

if(!

calculated_){

calculated_=true;

try{

performCalculations();

}catch(...){

calculated_=false;

throw;}

}

}

};

代码很简单,布尔变量calculated被定义用于跟踪结果是否被计算和有效,update方法实现观察者模式接口,当被观察变量有更新时被调用,calculated在该函数中被置为false目的是取消前一个结果的有效性。

calculate方法通过模板方法设计模式实现,就像在《GangofFourbook》中所解释的那样,算法的常量部分在基类中实现,变量部分被委托给一个虚拟方法,即performCalculations,该函数在基类实现中被调用,因此继承类仅仅实现具体的计算即可而不必关心缓存,相关的代码被嵌入在基类中。

缓存的机制也很简单,如果当前结果不再有效,我们让继承类执行必要的方法并更新结果标志,如果当前结果有效,我们不做任何事情。

然后,实现却并不是简单的。

你会发现在设置calculated前插入一个try块和再次抛出异常前进行回滚操作是很有道理的。

毕竟,我们也可以更为简单的实现该算法,就像如下所示:

if(!

calculated_){

performCalculations();

calculated_=true;

}

在许多情况下都会出现这样的情形(比如,当lazy对象是收益率期限结构时),performCalculations会递归调用calculate,在calculated被设置成true前,if条件继续保持并且performCalculations会被再次调用,这会导致无限递归,设置这样一个标志可以阻止这种现象发生。

然而我们也需要更多关注在抛出异常前将标志重新置为false,异常被抛出的目的在于可以被后续的错误处理程序扑获。

LazyObject中提供一个多种方法使用户可以阻止或强迫结果的重新计算,对此我们在这里不再讨论。

我再次建议有兴趣的读者就像Obi-WanKenobi常说的那样,“多读源码,Luke”。

表2.3:

类Instrument

classInstrument:

publicLazyObject{

protected:

mutableRealNPV_;

public:

RealNPV()const{

calculate();

returnNPV_;

}

voidcalculate()const{

if(isExpired()){

setupExpired();

calculated_=true;

}else{

LazyObject:

:

calculate();

}

}

virtualvoidsetupExpired()const{

NPV_=0.0;

}

}

Instrument类继承自LazyObject,为了实现列表2.1中的接口,该类针对具体的金融产品修饰了calculate方法,其结果如列表2.3所示,里面也加入了一些其他代码。

另外,增加的代码通过模板方法模板代理继承类执行具体金融产品的计算。

该类定义了一个NPV_数据成员保存计算结果,继承类可以声明其他数据成员保存特殊的结果。

在calculate方

思考:

Const或非Const?

解释一下NPV_为何被声明为mutable是有趣的,这个问题常出现于缓冲或被动计算的代码中,事情的关键在于从逻辑上讲NPV方法是一个常量方法:

计算金融产品的值并不必修改它。

因此用户有权去将方法声明为一个const实例。

反过来,NPV常量方法又迫使我们将calculate和performCalculations也声明为常量方法.但是,这种计算方法的选择及保存结果用于后续使用又使在这些方法的实现体重给一些数据成员赋值变的很有必要,我们通过声明缓存变量为mutable的方法解决。

这使我们(也包括继承类的开发者)同时满足这两种需求,即NPV常量方法和数据成员的赋值。

 

法中调用虚拟函数isExpired方法去检查该产品是否过期,如果过期了,则调用另外一个虚拟方法即setupExpired,该方法将计算结果设置为有意义的数值,它默认把_NPV设置为0并且被继承类调用。

然后将Calculated_标志设置为true。

如果金融产品没有过期,LazyObject的calcuate方法被调用,它反过来调用performCalculations。

这也给后续方法增加了一个契约,即在继承类它的实现需要设置NPV_为计算结果(也包括其他具体金融产品的数据成员)。

最终,NPV方法确保calculate方法在返回结果前被调用。

2.1.3.示例:

利率互换

我将通过基于上述类来完成一类具体的金融产品的实现来结束本节。

被选择的金融产品是利率互换。

就算用户很清楚的那样,该产品是一种约定期限内交换现金流的金融合约。

该产品的净值通过增加或减少基于现金流的流向进行折现的现金流数量进行计算。

毫无疑问,互换通过继承Instrument类来实现的。

它的框架如列表2.4所示。

它包含进行

表2.4:

Swap类的部分接口

classSwap:

publicInstrument{

public:

Swap(constvector>&firstLeg,

constvector>&secondLeg,

constHandle&termStructure);

boolisExpired()const;

RealfirstLegBPS()const;

RealsecondLegBPS()const;

protected:

//methods

voidsetupExpired()const;

voidperformCalculations()const;

//datamembers

vector>firstLeg_,secondLeg_;

HandletermStructure_;

mutableRealfirstLegBPS_,secondLegBPS_;

};

计算的数据成员对象,即进出现金流和收益率期限结构用于现金流折现及保存其他结果的两个变量。

另外,它声明了Instrument类接口和其他返回互换结构的方法,Swap类和相关的类图如图2.1所示。

在本节的剩余部分,我将回顾一下类的实现,相关方法将表2.5所示。

基于Instrument类实现具体的金融产品类需要三步,根据具体的派生类第三步是可选的。

图2.1Swap类图

第一步在类的构造函数中实现,构造函数接受参数(将参数拷贝到数据成员变量中)即用于交换的现金流和收益期限结构用于现金流折算,该步本身将Swap类注册为现金流和期限结构的观察者。

就如前面所述,现金流和期限结构有变化时将通知Swap并触发其重新进行计算。

第二步是一些必要接口的实现,isExpired方法的实现逻辑很简单,通过循环检查现金流的支付日期,如果发现依然有支付未发生则互换没有过期。

否则过期,在这种情况下setupExpired方法被调用。

它的实现调用基类中的setupExpired方法,而后检查继承自Instrument类的数据成员并将其置为0。

最后一个方法是performCalculations。

通过调用CashFlow中的函数该进行计算。

第一个,即npv函数,它循环检查现金流序列并增加未来现金流的贴现。

我们把NPV_赋值为进出现金流的差额。

第二个方法为bps,它计算现金流的BPS。

我们在进出现金流中都调用该方法并将结果保存到对应的数据成员里,如果结果没有任何数值错误,则errorEstimate_变量被置为Null()-一个特殊类型的浮点型数值代表一个非法的数据。

第三步即最后一步仅仅是有其他的结果需要定义的时候才需要实现,它包含写相应的方法

思考:

Handle还是共享指针?

你也许很疑惑为什么在Swap构造函数中接受贴现收益率参数为Handle而现金流参数为简单的

共享指针。

理由是我们或许需要改变收益率参数(通过调用Handle的LinkTo函数实现),而

现金流参数是Swap类的一部分并需要改变。

表2.5

voidSwap:

:

setupExpired()const{

Instrument:

:

setupExpired();

firstLegBPS_=secondLegBPS_=0.0;

}

voidSwap:

:

performCalculations()const{

NPV_=−Cashflows:

:

npv(firstLeg_,**termStructure_)

+Cashflows:

:

npv(secondLeg_,**termStructure_);

errorEstimate_=Null();

firstLegBPS_=−Cashflows:

:

bps(firstLeg_,**termStructure_);

secondLegBPS_=Cashflows:

:

bps(secondLeg_,**termStructure_);

}

RealSwap:

:

firstLegBPS()const{

calculate();

returnfirstLegBPS_;

}

RealSwap:

:

secondLegBPS()const{

calculate();

returnsecondLegBPS_;

}

(这是有firstLegBPS和secondLegBPS)在返回结果之前被计算执行。

实现已经完成了。

Swap类受益于Instrument类中实现的代码。

它将自动根据输入变化缓存和重新计算结果-即使除了注册调用外没有相应的代码在Swap中实现。

2.1.4.进一步开发

你也许在前面的例子和Instrument类中发现了一些缺点。

通俗地讲,在利率互换例子中我们并不能用不同的货币来进行进出现金流的支付。

用户在求和计算两种

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 经管营销 > 经济市场

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1