与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'])