第 8 章 学会用指数
从第4章开始,我们就一直在学习策略的编写和收益数据的计算,那么如何知道自己的策略在整个股票市场中处于一个什么样的水平呢,是强还是弱?这就要用到指数。
8.1 指数概念
指数是由专业机构编制,用一整套严谨的方法(选股规则、计算方法、权重规则),将一组具有代表性的股票编织在一起,最终计算得出一个反映这组股票整体价格水平的数值。常见的股票指数有:上证指数、深证指数、沪深300指数、中证500指数、创业板指、科创50指数等。
你可以简单的把指数理解为平均数,就像学校里给班级的同学计算平均成绩一样,股票也被按照不同的标准分进了不同的"班级”(例如上证50就像“尖子班”是由上海市场市值前50的公司组成的指数 ,科创板50就像“特长班”由科创板中的最好的前50家公司构成),这些“班级"的全体“同学”的平均成绩(即股票价)就是指数。
对于投资者来说了解并且会使用指数很重要:
(1)指数是市场的晴雨表。它能非常快速地告诉你整个市场(或某个板块)的大方向。不用看几千只股票,看一个数字的涨跌就知道了市场情绪。新闻里常说“大盘涨了”、“大盘跌了”,指的就是这个(通常是上证指数、深证成指、创业板指等)。
(2)指数是业绩基准。就像比赛得有标杆一样,如果你自己买了股票或者基金,会拿自己的收益和指数比一比,看看你比市场平均水平强,还是弱?
(3)指数是投资工具的基础。指数本身不能直接买,但你可以通过购买指数基金(ETF等)来间接投资整个指数,相当于买了那一篮子代表股票。这很流行,因为分散风险(鸡蛋不放在一个篮子里),而且费用通常比较低。
8.2 指数数据
和股票数据一样,指数也有K线和代码列表。
在下载链接中yyyyMMdd.index.btx 文件为yyyyMMdd日之前的指数日线行情;
yyyy.index.btx 文件为yyyy当年的指数日线行情;indexlist.csv则是指数代码列表。
我们先来看一下指数代码列表,直接用T函数打开:
A |
|
1 |
=T("indexlist.csv") |
代码列表中包含了指数代码、名称、所属交易所等信息
再来看一下指数的日线行情数据,和股票K线一样,我们编写一个读数脚本来读数。
脚本代码:
A |
|
1 |
=if(ifv(DATAPATH),DATAPATH,"") |
2 |
=year(now()) |
3 |
=end=ifn(end,now()) |
4 |
=if(year(start)<A2,(A2-1)*10000+1231)|if(year(end)==A2,A2) |
5 |
=A4.(file(A1/~/".index.btx")).select(~.exists()) |
6 |
=cl=if(ifa(cl), cl.sort(),cl) |
7 |
=if(cl, A5.( ~.iselect@b(cl,code)), A5.(~.cursor@b()) ) |
8 |
=A7.merge(code,tdate).select(tdate>=start && tdate<=end) |
9 |
return A8.fetch@x() |
将脚本保存为“loadindex.splx”, 此脚本可返回一支或多支指数任意时间段的 K 线数据。对于初学者,这段脚本也是先会用即可。
脚本参数:
cl |
指数代码序列,如 [1,2];可以为单值,如 1;填空表示全读 |
start |
开始日期,如2024-01-01 |
end |
截止日期,如2024-12-31。end为空时截止到当前日期 |
我们把这个脚本登记也加到init.splx里,登记为Index函数,然后直接调用就可以了。
A |
|
… |
…… |
7 |
>register("Index", "loadindex.splx") |
调用脚本,读取上证综合指数的日线行情:
A |
B |
|
1 |
>call("init.splx") |
将脚本登记为函数 |
2 |
1 |
指数代码 |
3 |
2025-01-01 |
开始日期 |
4 |
2025-01-10 |
截止日期 |
5 |
=Index(A2,A3,A4) |
返回开始到截止日期的数据 |
6 |
=Index(A2,A3) |
end为空,截止到当前日期 |
7 |
=Index(,A3,A4) |
cl为空,读取全部指数 |
和股票K线一样,指数K线中也包含价格、成交量等信息
8.3 将策略收益和指数做对比
既然指数是市场的标杆,那么我们就将自己的策略和指数做一个对比,看看表现如何。由于这个对比具备通用性,我们写成一个脚本。
首先明确脚本功能,我们希望输入某个策略每日收益数据以及要对比的指数代码,就可自动绘制出策略收益和指数的对比图形。
定义参数如下:
Y |
序表,某策略的每日收益,也就是回测例程中Yield函数的输出结果 |
ino |
数字,指数代码 |
file |
字符串,输出图形文件,如"compare.html" |
下面开始编写代码,进行对比。对比的方法为,将首日的资产值和市场指数价格设为一致比如100元,然后对比每日资产的涨幅是否能超过指数涨幅。
因此代码可以按如下步骤编写:
(1) 读取指数数据
(2) 根据收益数据,计算每日总资产,并将首日资产设为100,其余日期等比例转换。
(3) 将指数数据和每日资产数据关联到一张表。并用同样方法,将指数首日价格设为100,其余日期价格等比例转换
(4) 绘制转换后数据的对比动态图
A |
B |
|
1 |
=Y(1).日期 |
=Y.m(-1).日期 |
2 |
=Index(Ino, A1, B1) |
|
3 |
=-Y.min(现金) |
=Y.new(日期, (收益+A3)*100/A3:策略) |
4 |
=100/A2.open |
|
5 |
=B3.join(日期, A2:tdate, close:指数).run(指数*=A4) |
|
6 |
=Draw( A5, "日期","策略,指数", null, file ) |
A1:A2 读取指数数据
A3 计算占用资金
B3 占用资金+每日收益=每日资产值,并按首日100等比例转换
A4 计算指数价格转变比例。以首日开盘价为基准,设为100
A5 关联表格,将指数数据拼接到每日资产数据上,并等比例转化指数价格。
A6 以日期为横坐标,策略和指数为纵坐标,绘制动态图
代码写好后,脚本保存为”compare.splx”,并且在”init.splx”脚本中登记为Compare函数。
A |
|
… |
…… |
8 |
>register("Compare", "compare.splx") |
然后就可以调用脚本函数将策略和指数进行对比了,例如将6.3小节的均价策略和上证50指数进行对比:
A |
B |
C |
|
… |
…… |
…… |
…… |
14 |
=@|Summary(B9) |
=Yield(@|B9) |
|
15 |
=Total(B14,C14) |
=Display(A15) |
|
16 |
>Compare(C14, 16,"compare.html") |
A16 返回策略和指数动态对比图
8.4 指数相关的指标
有些指标需要同时用到 K 线和指数数据,例如有一个叫做相对市场波动率的指标,它的计算方法是这样的:
相对市场波动率 =(股票涨幅 - 市场涨幅)的 N 日标准差 * sqrt(N 日)
其中市场涨幅就是某个指数的涨幅数据需要从指数数据中读取计算,股票涨幅则来自于 K 线数据。显然这类指标需要将用到的指数数据和 K 线拼接到一起,两张表格拼接我们学过用 join 函数就可以,但是 join 之后会产生新的表格,这样就无法将计算结果返回到原表上,不符合我们定义的指标编写规则。因此我们需要换一种方法,先在 K 线上增加需要拼接的列,然后用 find 函数去找对应的的值,赋给该列。为方便使用,我们将这个拼接功能编写成一个自定义函数。函数脚本如下:
A |
B |
|
… |
… |
…. |
30 |
func EXT@o(o, A, S, cols) |
=cols.split@c().( _c=~.split(":"), ifn(_c(2),_c(1))/"=_r."/_c(1)).concat@c() |
31 |
>output(B30) |
|
32 |
=if(o=="c", "代码,","" ) |
|
33 |
=A.run(_r= S.find( ${B32} 日期 ), ${B30} ) |
将这个函数保存到脚本 indicator.splx 里。
使用这个函数可以实现将指数数据或公司基本面数据(下一章会讲)拼接到 K 线上。
这个函数代码我们不再解释,有兴趣的读者可自行研究,这里我们重点学习如何使用这个函数。
函数的参数含义如下:
o |
o值有两种:填空表示拼接指数数据,填”c”表示拼接公司基本面数据。函数的选项@o表示将函数的第一个参数设为选项 |
A |
序表,K线数据 |
S |
序表,指数数据或公司基本面数据 |
col |
要拼接的列,书写形式是 “源列名:目标列名,…” |
例如我们将沪深 300 指数的收盘价拼接到股票 600000 的 K 线上:
A |
B |
|
1 |
>call("init.splx") |
|
2 |
2024 |
=date(A2,1,1) |
3 |
=B2-100 |
=date(A2,12,31) |
4 |
600000 |
=Load@C(A4, B2,B3) |
5 |
=Index(399300, B2, B3).derive().keys@i(tdate) |
|
6 |
=B4.derive(:指数收盘 ) |
|
7 |
=EXT( A6, A5, “close:指数收盘” ) |
A1:B4 读取 K 线数据
A5 读取指数数据,并将 tdate 设置为主键。在 EXT 函数里我们使用了 find 函数根据日期来查找对应的 K 线数据,A.find(K) 表示在表 A 中查找主键为 K 的成员。因此在查找之前要用 keys 函数给指数数据设置一个主键,A.keys(tdate) 表示把时间作为主键。主键是人为指定的,并且是唯一的,这样 find 函数就能找到准确的值了。A5 格中 keys 加选线 @i 表示在指定主键的同时创建索引,这样可以提高查找效率。在使用 keys 函数时还要注意它只能在序表设置主键,而 Index 读取出来的数据是用游标 fetch 出来的,是一截截拼出来的并非序表,因此在 keys 之前要 derive 一下,将结果变为序表。
A6 在 K 线数据上增加指数收盘列
A7 调用 EXT 函数将指数数据 A5 中的 close 值拼到 A6 的指数收盘列,这样就将指数的收盘价拼接到了 K 线数据上了。
下面我们使用 EXT 函数来编写一下市场波动率指标。
定义函数的输入参数有:K 线数据 A,指数数据 S,时间周期 n,输出值就是相对市场波动率 y。
首先用今日收盘 - 昨日收盘分别计算出股票涨幅和市场涨幅,然后将市场涨幅拼接到 K 线上,带入公式计算出市场波动率。
函数代码:
A |
B |
|
… |
… |
… |
35 |
func RMV(A,$y,S,n) |
=A.derive@o(:rmv_pctChga,:rmv_pctChgs,:rmv_rchange,:rmv_close) |
36 |
=EXT(A, S,"close:rmv_close") |
|
37 |
=A.run((收盘-收盘[-1])/收盘[-1]:rmv_pctChga) |
|
38 |
=A.run((rmv_close-rmv_close[-1])/rmv_close[-1]:rmv_pctChgs) |
|
39 |
=A.run(rmv_pctChga-rmv_pctChgs:rmv_rchange) |
|
40 |
=A.run(sqrt(var@s(rmv_rchange[1-n:0]))*sqrt(n):${y}) |
|
41 |
=A.alter(;rmv_pctChga,rmv_pctChgs,rmv_rchange,rmv_close) |
调用公式计算股票 600000 的 250 日相对沪深 300 波动率:
A |
B |
|
1 |
>call("init.splx") |
|
2 |
2024 |
=date(A2,1,1) |
3 |
=B2-365 |
=date(A2,12,31) |
4 |
600000 |
=Load@C(A4, A3,B3) |
5 |
=Index(399300, A3, B3).derive().keys@i(tdate) |
|
6 |
=B4.derive(:RMV) |
|
7 |
=RMV(A6,RMV,A5,250) |
计算结果: