与Pandas配合使用¶
当我受启发开始开发RiskQuantLib的时候,我的朋友问了我一个问题:
你是在试图重新造一个Pandas吗?
我承认在某种意义上,RiskQuantLib和Pandas非常像,但在一开始,RiskQuantLib并不是被设计用于替代Pandas的,它被设计用于在Pandas之后进行进一步的数据处理时使用。RiskQuantLib应该是一个数据处理内核,专注于数据处理逻辑,而不是作为一个像Pandas一样的集成工具,涵盖从数据输入到输出的方方面面。
但之后当我们重新讨论这个问题的时候,我意识到如果要摆脱Pandas的诅咒,我应该先摆脱更多的传统观念。这导致了你现在看到的RiskQuantLib,其中的模板列表类有很多默认的自定义函数,比如迭代函数和合并函数,这些函数有很多类似Pandas的地方。
但是,始终有一件事值得注意:
RiskQuantLib依然不是Pandas的替代品,它是基于Pandas的。最好的方式是用RiskQuantLib来编写数据处理的逻辑内核,而把其余部分交给Pandas处理。
为了方便,RiskQuantLib提供了很多用于与Pandas交互的函数,它们如下:
从Pandas Series转换为RiskQuantLib模板列表¶
使用pandas最简单的方式是使用pandas.Series,如果你有一个数据表,看起来像这样:
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 |
… |
… |
… |
… |
… |
… |
… |
在创建了一个工程项目,并且通过以下内容的 config.py 文件声明了构建方式,然后运行了构建命令后:
#-|instrument: myEuropeanOption
#-|instrument-ParentQuantLibClassName: myEuropeanOption@EuropeanOption
#-|instrument-DefaultInstrumentType: myEuropeanOption@myEuropeanOption
#-|attribute: myEuropeanOption.myPayOff@qlPayOff, myEuropeanOption.myExercise@qlExercise, myEuropeanOption.underlyingStockPrice@qlQuote, myEuropeanOption.riskFreeRate@qlQuote, myEuropeanOption.sigma@qlQuote
你就可以打开 main.py 文件,然后直接像这样使用模板类和模板列表类:
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'])
我们知道 set 函数族接受任何可迭代对象作为参数,自然,你可以传入pandas.Series给它。通常, set 函数族有两个参数,第一个参数用于声明哪些元素需要被改变,第二元素声明你想将属性的值改变为什么。
从Pandas DataFrame转换为RiskQuantLib模板列表¶
你或许要问,我如果有一个数据表,它的列数非常多,甚至有一千多列,那么我总不能一列一列地使用Set函数吧?
没错,好在RiskQuantLib提供了一个现成的函数,便于从pandas.DataFrame中读取数据,我们假设你想要读取的数据表依然是之前的样子:
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 |
… |
… |
… |
… |
… |
… |
… |
现在我们更改 config.py 文件,使得它看起来像这样:
#-|instrument: myEuropeanOption
#-|instrument-ParentQuantLibClassName: myEuropeanOption@EuropeanOption
#-|instrument-DefaultInstrumentType: myEuropeanOption@myEuropeanOption
#-|attribute: myEuropeanOption.PayOff@qlPayOff, myEuropeanOption.ExerciseDate@qlExercise, myEuropeanOption.StockPrice@qlQuote, myEuropeanOption.RiskFreeRate@qlQuote, myEuropeanOption.Sigma@qlQuote
注意到这里,我们所有的属性名称都和数据表的列名相同。这会使得RiskQuantLib能够自动识别列,并对应Set函数,来自动使用它们。
当编译完成后,你可以打开 main.py 并且这样使用::
from RiskQuantLib.module import *
df = pd.read_excel(path+os.sep+'European_Option.xlsx')
vanillaOptionList = myEuropeanOptionList().fromDF(df,code = 'OptionCode')
但我并不建议这样使用,因为RiskQuantLib并不应该与单个数据表绑定,如果你这样做,你有可能会遇见这样的情况,即你在数据处理中遇到了另一个数据表,他的列名和第一个数据表并不相同,但实际的意义是相同的,比如这里我们有一个数据表 df2,它看起来像这样:
Code |
Pay |
Type |
KDate |
Price |
RF |
Vol |
|---|---|---|---|---|---|---|
C |
PlainVanilla |
European |
2021-11-18 |
103.5 |
0.03 |
0.16 |
D |
PlainVanilla |
European |
2022-03-20 |
88.1 |
0.019 |
0.10 |
… |
… |
… |
… |
… |
… |
… |
如果你想要将这些合约添加到RiskQuantLib的模板列表,你可以使用 addFromDF,而不是 fromDF:
vanillaOptionList.addFromDF(df2,code = 'Code')
但是,因为df2的列名并不完全等于你注册过的属性名,你会发现这些你添加的新元素没有任何属性值。
有两个办法来解决这一问题,一是是你可先更改df2的列名,并更新模板列表的元素。或者你可以手动使用Set函数族。我们先解释第一种方法。
从DataFrame更新元素¶
当你对df2重新命名后,它看起来像这样:
OptionCode |
PayOff |
ExerciseType |
ExerciseDate |
StockPrice |
RiskFreeRate |
Sigma |
|---|---|---|---|---|---|---|
C |
PlainVanilla |
European |
2021-11-18 |
103.5 |
0.03 |
0.16 |
D |
PlainVanilla |
European |
2022-03-20 |
88.1 |
0.019 |
0.10 |
… |
… |
… |
… |
… |
… |
… |
如果你还记得,你已经添加了元素C和D到 vanillaOptionList,我们现在要做的是更新C和D的属性,我们可以这样做::
vanillaOptionList.updateAttrFromDF(df2, code = 'OptionCode')
当这一步完成后,合约C和D的属性会被更新,A和B的属性则不受影响。但我们应该在此注意:
工程的编译不应该基于数据模型,而应该基于分析的逻辑。
也就是说,当你设计自己的工程时,你应该忘记你的输入文件是什么样子的,忘记你有几个输入文件,忘记你是如何得到这些输入文件的,忘记你打算如何输出你的运算结果,忘记你的运算结果应该以什么格式展现。你应该思考你的数据处理过程中,应该使用什么样的模板类,这些模板类之间应该怎样联系起来,每个模板类应该具有哪些属性,是否可以使用更少的属性来达成目的,每个属性应该对应什么数据类型,为什么自定义的数据类型可以简化这个数据处理任务,等等。
输出为Pandas DataFrame¶
最直接的方法是这样::
result = pd.DataFrame(vanillaOptionList[attributeList])
这里的 attributeList 是一个python的list,它的元素是属性的名称。你也可以在使用此命令转换为pandas.DataFrame的时候指定索引,比如::
result = pd.DataFrame(vanillaOptionList[attributeList],index = vanillaOptionList['code'])
或者你可以使用更简单的方式:
result = vanillaOptionList[attributeList].toDF(index=vanillaOptionList['code'])