与QuantLib配合使用¶
曾有一段时间,在Stack Overflow上有一个有趣的问题:
为什么使用QuantLib进行期权定价如此繁琐,就算一个简单的香草期权,也要使用很多行代码来实现?
确实如此,当你使用QuantLib定价香草期权,你可能会这样做::
# Set Evaluation Date
today = ql.Date(18, 3, 2021)
ql.Settings.instance().evaluationDate = today
# Build An Option
# Set Payoff
payoff=ql.PlainVanillaPayoff(ql.Option.Call, 100.0)
# Set Exercise Date
europeanExercise=ql.EuropeanExercise(ql.Date(18, 11, 2021))
option = ql.EuropeanOption(payoff, europeanExercise)
# Set Initial Stock Price, Risk-Free Rate, Volatility
u = ql.SimpleQuote(100.0) # Initial Stock Price
r = ql.SimpleQuote(0.05) # Risk-Free Rate
sigma = ql.SimpleQuote(0.20) # Volatility
# Set Term Structure Of Curve
riskFreeCurve = ql.FlatForward(0, ql.TARGET(), ql.QuoteHandle(r), ql.Actual360())
volatility = ql.BlackConstantVol(0, ql.TARGET(), ql.QuoteHandle(sigma), ql.Actual360())
# Initialize Stochastic Process And Pricing Enging
process = ql.BlackScholesProcess(ql.QuoteHandle(u),
ql.YieldTermStructureHandle(riskFreeCurve),
ql.BlackVolTermStructureHandle(volatility))
engine = ql.AnalyticEuropeanEngine(process)
# Set Enging To Option
option.setPricingEngine(engine)
# Print Result
print(f'Option Value:{option.NPV():.4f}')
print("%-12s: %4.4f" %("Delta", option.delta() ))
print("%-12s: %4.4f" %("Gamma", option.gamma() ))
print("%-12s: %4.4f" %("Theta", option.vega()))
但如果你有十个期权,想对他们同时进行定价,或者你只是想探索一下,期权的价格是如何随着市场情况改变的,要如何方便地做到呢?无论你是学生还是衍生品交易员,你有时可能会有一个数据表,它看起来像这样:
OptionCode |
PayOff |
ExerciseType |
ExerciseDate |
StockPrice |
RiskFreeRate |
Sigma |
|---|---|---|---|---|---|---|
A |
PlainVanilla |
European |
2021-11-18 |
100.0 |
0.05 |
0.20 |
B |
PlainVanilla |
European |
2022-03-20 |
97.6 |
0.032 |
0.17 |
… |
… |
… |
… |
… |
… |
… |
在RiskQuantLib中进行期权定价,我们需要创建我们自己的欧式期权定价模板。首先我们使用终端命令来新建一个工程::
newRQL yourProjectPath
之后,我们打开 config.py,编辑并使得它看起来像这样:
#-|instrument: myEuropeanOption
#-|instrument-ParentQuantLibClassName: myEuropeanOption@EuropeanOption
#-|instrument-DefaultInstrumentType: myEuropeanOption@myEuropeanOption
之后我们继续向 config.py 文件添加内容:
#-|attribute: myEuropeanOption.myPayOff@qlPayOff, myEuropeanOption.myExercise@qlExercise, myEuropeanOption.underlyingStockPrice@qlQuote, myEuropeanOption.riskFreeRate@qlQuote, myEuropeanOption.sigma@qlQuote
然后我们使用终端命令来编译工程::
cd yourProjectPath
python build.py
现在我们有了一个全新的RiskQuantLib来便于进行欧式期权定价,RiskQuantLib已经自动创建了模板类,数据类型类。我们要更改这些类文件,使得他们符合我们的数据处理项目。我们打开并编辑 RiskQuantLib/Property/QlExercise/qlExercise.py,使得它看起来像这样::
#!/usr/bin/python
# coding = utf-8
import QuantLib as ql
import pandas as pd
from RiskQuantLib.Property.property import property
class qlExercise(property):
def __nullFunction__(self):
pass
def __init__(self, value : pd.Timestamp):
value = ql.EuropeanExercise(ql.Date(value.day, value.month, value.year))
super(qlExercise,self).__init__(value)
def setValue(self,value : pd.Timestamp):
self.value = ql.EuropeanExercise(ql.Date(value.day, value.month, value.year))
在这一步,我们打包了QuantLib中关于行权的代码,以便于我们在仅仅传入一个到期日期的情况下使用它。当这一步完成后,我们就不需要每定价一个欧式期权都定义一次行权。
接着,我们打开并编辑 RiskQuantLib/Property/QlPayOff/qlPayOff.py,使得它看起来像这样::
#!/usr/bin/python
# coding = utf-8
import QuantLib as ql
from RiskQuantLib.Property.property import property
class qlPayOff(property):
def __nullFunction__(self):
pass
def __init__(self, value : float):
value = ql.PlainVanillaPayoff(ql.Option.Call, value)
super(qlPayOff,self).__init__(value)
def setValue(self, value : float):
self.value = ql.PlainVanillaPayoff(ql.Option.Call, value)
在这一步,我们打包了QuantLib中关于回报的代码,以便于我们可以在仅传入回报值的情况下使用。这一步完成后,我们就不需要每定价一个欧式期权都定义一次回报。
接着,我们打开并编辑 RiskQuantLib/Property/QlQuote/qlQuote.py,使得它看起来像这样::
#!/usr/bin/python
# coding = utf-8
import QuantLib as ql
from RiskQuantLib.Property.property import property
class qlQuote(property):
def __nullFunction__(self):
pass
def __init__(self, value : float):
value = ql.SimpleQuote(value)
super(qlQuote,self).__init__(value)
def setValue(self,value):
self.value.setValue(value)
最后的准备工作是编辑 RiskQuantLib/Instrument/Security/MyEuropeanOption/myEuropeanOption.py,使得它看起来像这样::
#!/usr/bin/python
# coding = utf-8
import QuantLib as ql
from RiskQuantLib.Instrument.Security.security import security
from QuantLib import EuropeanOption
from RiskQuantLib.Auto.Instrument.Security.MyEuropeanOption.myEuropeanOption import setMyEuropeanOption
class myEuropeanOption(security,EuropeanOption,setMyEuropeanOption):
def __nullFunction__(self):
pass
def __init__(self, codeString,nameString,securityTypeString = 'myEuropeanOption'):
security.__init__(self,codeString,nameString,securityTypeString)
def iniPricingModule(self, *args):
EuropeanOption.__init__(self,*args)
def pricing(self):
self.iniPricingModule(self.myPayOff,self.myExercise)
riskFreeCurve = ql.FlatForward(0, ql.TARGET(), ql.QuoteHandle(self.riskFreeRate), ql.Actual360())
volatility = ql.BlackConstantVol(0, ql.TARGET(), ql.QuoteHandle(self.sigma), ql.Actual360())
process = ql.BlackScholesProcess(ql.QuoteHandle(self.underlyingStockPrice),
ql.YieldTermStructureHandle(riskFreeCurve),
ql.BlackVolTermStructureHandle(volatility))
engine = ql.AnalyticEuropeanEngine(process)
self.setPricingEngine(engine)
# Calculate Result
self.npvValue = self.NPV()
self.deltaValue = self.delta()
self.gammaValue = self.gamma()
self.vegaValue = self.vega()
现在一切都准备好了,我们换到项目根目录下的 main.py 文件,定价刚才提到的期权,我们只需要::
# With RiskQuantLib
# Don't Forget to Set Evaluation Date
today = ql.Date(18, 3, 2021)
ql.Settings.instance().evaluationDate = today
from RiskQuantLib.module import *
vanillaOption = myEuropeanOption("A","A")
vanillaOption.setMyPayOff(100)
vanillaOption.setMyExercise(pd.Timestamp("20211118"))
vanillaOption.setUnderlyingStockPrice(100)
vanillaOption.setRiskFreeRate(0.05)
vanillaOption.setSigma(0.20)
vanillaOption.pricing()
代码变得更加可读了不是么。更重要的是,你可以更改参数的值,来方便地进行重定价。如果需要对一个新的期权进行定价,只需要初始化另一个实例::
# You already set valuation date, don't need to do it again.
from RiskQuantLib.module import *
vanillaOption = myEuropeanOption("B","B")
vanillaOption.setMyPayOff(100)
vanillaOption.setMyExercise(pd.Timestamp("20220320"))
vanillaOption.setUnderlyingStockPrice(97.6)
vanillaOption.setRiskFreeRate(0.032)
vanillaOption.setSigma(0.17)
vanillaOption.pricing()
或者你可以更优雅地做到这一点,那就是使用RiskQuantLib模板列表。记得我们有一个这样的数据表:
OptionCode |
PayOff |
ExerciseType |
ExerciseDate |
StockPrice |
RiskFreeRate |
Sigma |
|---|---|---|---|---|---|---|
A |
PlainVanilla |
European |
2021-11-18 |
100.0 |
0.05 |
0.20 |
B |
PlainVanilla |
European |
2022-03-20 |
97.6 |
0.032 |
0.17 |
我们在项目根目录下将它保存为excel文件,命名为 European_Option.xlsx,接着在 main.py 我们这样编写代码:
# With RiskQuantLib List
# Set Evaluation Date
today = ql.Date(18, 3, 2021)
ql.Settings.instance().evaluationDate = today
from RiskQuantLib.module import *
df = pd.read_excel(path+os.sep+'European_Option.xlsx')
vanillaOptionList = myEuropeanOptionList()
vanillaOptionList.addMyEuropeanOptionSeries(df['OptionCode'],df['OptionCode'])
vanillaOptionList.setMyPayOff(df['OptionCode'],[100 for payoff in df['OptionCode']])
vanillaOptionList.setMyExercise(df['OptionCode'],[pd.Timestamp(date) for date in df['ExerciseDate']])
vanillaOptionList.setUnderlyingStockPrice(df['OptionCode'],df['StockPrice'])
vanillaOptionList.setRiskFreeRate(df['OptionCode'],df['RiskFreeRate'])
vanillaOptionList.setSigma(df['OptionCode'],df['Sigma'])
vanillaOptionList.execFunc('pricing')
如果你想保存结果,你只需要将它转换为dataframe::
result = pd.DataFrame(vanillaOptionList[['code','npvValue','deltaValue','gammaValue','vegaValue']])
别忘了将你的工程保存为工程模板,这样的期权定价模板可以被反复使用,这是RiskQuantLib很重要的一点。
为了保存为模板,你应该首先删除 European_Option.xlsx,并且清空 main.py 中的代码,因为这些数据和操作是不可以被反复使用的,模板工程只需要保留数据处理的逻辑。当做完了这些后,你可以打开终端运行命令::
saveRQL yourProjectPath europeanOption
当下次你遇见欧式期权定价任务时,你可以使用终端命令来恢复模板工程::
tplRQL europeanOption yourNewProjectPath