请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化平台 帖子:3354127 新帖:29

【量化课堂】指标效果的统计分析:思路之一

爱汇小王子发表于:5 月 10 日 05:38回复(1)

我们经常会在交易中使用一些技术指标,但很多时候只是知道这个指标的核心思路,并不太确定它们的实际效果。可能它们的效果和我们的想法有偏差。可能它们的效果是因股票而异的。也可能我们连一个具体的思路都不清楚,只是天马行空地想到,“咦?这个也许能行?”

我们把这些指标写到策略里,有时候回测看着像这么回事,可有时候又不像那么回事,懵懵懂懂的我们也不知道是怎么回事了。

“那…如果,”你说,“嗯,不对,让我再想想…”

时过半晌,“如果,”你又打断了沉默,“有一个系统性的统计方法,能让我们直观地看到这个指标的效果,那就好了。”

诶哟你说得太对了,我就是来找你谈这个的。


统计的对象

现代金融理论认为,证券价格的时间序列在大部分时间服从随机游走,但是在一些特定的时刻(比如基本面发生变化,或者供需关系发生变化),价格序列会偏离随机游走,并且选择向上或者向下的方向。当价格在脱离随机游走时,通常都会产生一些现象或征兆,就是我们常说的“信号”,如果我们捕捉到这些信号,就可以赚取收益。

我们使用利用股票的历史数据计算各种各样的指标(比如MACD,RSI),就是为了通过指标的数值来判断是否出现信号。但很多时候指标的使用是粗糙并且模糊的,比如“当RSI低于20时买入”是一个发出信号的标准,但为什么是20呢?18或者22会不会效果更好?我们想通过历史统计来分析这个问题,那么进行统计的对象就有两个:指标出现的数值,以及出现该数值之后的涨跌结果。


指标

一般而言,一支股票的指标是一个函数Ind(T),它输入的是时间T(确切地说还有时间T之前的所有股票数据),返回的是在那个时间点的指标数值。比如说,我们要考量的指标是过去5天的收益率,那么指标的输出就是今日收盘价除以五天前收盘价的商再减1。


结果

结果指的就是,我们认为指标所预测的事件到底有没有发生,由此把结果分为“赢”、“平”和“输”。举例来说,假设我们认为过去5天的收益率越高,未来两天的收益率就越大;那我们要观测的结果就是未来两天交易量的情况,如果未来两天的平均交易量大于今天的110%,就记作“赢”,小于今天的100%就记作“输”,其余情况记作“平”。这里“赢平输”的计算标准有一定拍脑袋的成分,这么做的缺点就是统计的数据不全面,优点是结果更直观更方便应用,并且如果统计结果不理想,我们可以更改输赢的决定方法再重新来过。

指标和结果的统计

数据统计

一、提取历史所有交易日的股票数据,并去掉停牌日的信息;
二、记录每一天的指标和输赢结果。对于每一个不停牌的交易日T,做:

\qquad

a. 计算当日的指标Ind(T),方便统计需要,四舍五入到合适的小数点位;

\qquad

b. 计算从T日起观测的输赢结果;

\qquad

c. 记录事件组(Ind(T),赢或平或输)。
三、统计指标的某个值出现后赢和平和输分别发生过多少次。

举例个简单的例子。假设我们认为过去5天涨得越多则未来五天越可能跌。那么把五日收益率设为指标;未来第五天收盘价小于今日收盘价则记为赢,大于记输,等于则记平。

首先收集数据,注意要把停牌时的数据去掉,这里是从06年2月2日到16年2月2日。

prices = get_price('002200.XSHE', start_date="2006-02-02", end_date="2016-02-02", frequency='daily', fields=['close'], skip_paused=True)['close']

然后进行统计,第二步第三步可以一气呵成# 要用到pandasimport pandas as pddef get_stats(prices):# 设置一个计数器i = 5# 设置一个空listdata = []# 从第一个到倒数第二个价格while i < len(prices)-4:# 计算五日收益率,留小数点后2位ratio = 0.01 * ((prices[i]/prices[i-5]) //0.01)# 如果第五天收盘价更低if prices[i 4] < prices[i]:# 那结果记赢result = 'win'# 如果第五天收盘价更高if prices[i 4] > prices[i]:# 结果记输result = 'lose'# 收盘价不变的话if prices[i] == prices[i 4]:# 记平result = 'even'# 看看该比例有无记载过ratio_recorded = False# 翻看datafor data_dict in data:# 如果比例被记载过if data_dict['value'] == ratio:# 那就好,更新输赢data_dict[result]  = 1# 记载过为是ratio_recorded = True# 如果翻完了发现没记载过,if ratio_recorded == False:# 那么就记载下来data_dict = {'value':ratio, 'win':0, 'lose':0, 'even':0}
            data_dict[result]  = 1data.append(data_dict)# 别忘更新ii  = 1# 转换成DataFramedf = pd.DataFrame(data,  columns=['value', 'win','even', 'lose'])# 按照value列从小到大排序df = df.sort(['value'], ascending = True)return(df)

然后就可以看到结果啦

stats = get_stats(prices)
stats.head(10)


制图

把统计好的数据画出来就可以看出效果了。基于以上数据的特性,我们要画一个叠加条状图。该图的x坐标轴是指标的值,y轴则是该指标值出现的次数,并按颜色划分成“赢平输”叠加的条状图。代码如下:

# 要用到pyplot包Import matplotlib.pyplot as plt# 设置图的大小fig_size = plt.rcParams["figure.figsize"]
fig_size[0] = 15
fig_size[1] = 10# 把统计的DataFrame中竖列抽出来values = stats['value']
wins = stats['win']
evens = stats['even']
loses = stats['lose']# 画输的数目,颜色为红p1 = plt.bar(values, loses, width = 0.008, color='r')# 画平的数目,颜色为黄,底部在输之上p2 = plt.bar(values, evens, width= 0.008,  bottom = loses, color='y')# 画赢的数目,颜色为绿,底部在输 平之上p3 = plt.bar(values, wins, width=0.008, bottom = evens loses, color='g')# 标注坐标轴和注释plt.ylabel('输赢结果',size = 15)
plt.xlabel('收盘价除以22',size = 15)
plt.legend((p1[0], p2[0],p3[0]), ('输', '平','赢'))

得到下图

为了方便看出赢和输的差距,可以把赢减输画出来。

代码

# 画赢数减去输数pdifference = plt.bar(values, wins - loses, width = 0.008, color='b')# 标注坐标轴和注释plt.ylabel('赢减去输',size = 15)
plt.xlabel('5日收益率',size = 15)

得到

这样就可以清晰地看到一些规律。

合理地使用统计结果

统计结果不可以乱用
我们单单把图画了出来,那是不是就可以在策略中用了?当然不是。拿之前的统计举例,可以看见两个比较明显的问题

先看紫色标注的位置,那里统计的胜率很高。如果们选择在5日收益率1.17时买入,会怎么样?首先5日收益率正好为1.17的概率很小,可能若干年都不出现信号。其次,可以看见它右边的1.18的胜率并不高,那么实际上当指标计算出为1.175时就无法合理判断。

再看棕色框起的区域,这里只有赢没有输,但是这个区域中样本量太小了,完全无法保证统计结果的准确性。


选择胜率最高的区间

一个可行的解决办法,是设定一个指标区间的宽度w,并且设定最低数据比θ。如果一个宽度为w的x轴区间里的数据量占总数据量的θ以上,我们就计算并记录该区间里的输赢比;如果区间内数据比例少于θ,我们则抛弃该区间。最后选出输赢比最高的区间作为产生信号的指标值。

比如我们设w=4,那么在1.08到1.11的区间里,赢输比为66:58。

我们要选的是依照该计算方法得到的赢输比最高的区间。

下面函数的4个输入分别为:
statistics – 之前算出的DataFrame统计数据
tick_width – 指标值的最小间隔(跳动值,比如上面的示例里就是0.01)
least_percentage – 要求区间数据量至少有总数据量的多少
band_width – 区间的宽度,整数,可理解为进行多少次tick_width的跳动。

代码如下

# 返回一Series,内容 low=区间低点,high=区间高点,ratio=区间赢输比def find_best_region(statistics, tick_width, least_percentage, band_width):# 取出统计df的列values = statistics['value']
    loses = statistics['lose']
    wins = statistics['win']
    evens = statistics['even']# 算总数据量num_data = sum(wins)   sum(loses)   sum(evens)# 起始一listmydata = []# 计算指标统计出的最低值除以间距,取整数,方便后面移动计算。low_bound = int(statistics['value'].iloc[0]/tick_width)# 计算指标统计出的最高值除以间距,减去区间宽度high_bound = int(statistics['value'].iloc[-1]/tick_width - band_width   1)# 对于上限和下限之间的所有整数for n in range(low_bound, high_bound ):# 选取统计中所对应的区间statistics1 = statistics[values >= float(n)*tick_width]
        stat_in_range  = statistics1[values <= float(n   band_width - 1) * tick_width]# 计算区间中的赢输比。输数加一,避免除以零。ratio = float(sum(stat_in_range['win'])) / float(sum(stat_in_range['lose']) 1)# 计算区间中数据量range_data = float(sum(stat_in_range['win'])   sum(stat_in_range['lose'])   sum(stat_in_range['even']))# 如果区间数据量除以总数据量大于最低数据比if range_data / num_data >= least_percentage:# 记录区间的最低值,最高值,和区间内的赢输比mydata.append({'low': float(n) * tick_width, 'high': float(n band_width) * tick_width, 'ratio': ratio})# 制作DataFramedata_table = pd.DataFrame(mydata)# 按照赢输比排序sorted_table = data_table.sort('ratio', ascending = False)# 返回第一行return(sorted_table.iloc[0])

对于上面的例子,tick_width = 0.01,再设 least_percentage = 0.03,以及 band_width = 4,计算

find_best_region(stats,0.01, 0.03, 4)

得到区间[1.12,1.16],区间内输赢比1.43。


还不过瘾?
不过瘾好啊,那么与其固定区间的宽度,我们不如把所有的宽度都计算一遍。做法就是,先固定一个最短的宽度w_0,对于所有宽度为w_0的区间进行上面的计算,然后将宽度换为w_0 1再重复一遍,然后是w_0 2,以此类推,直到达到了设定的最大宽度。最后取这个过程中算出的最大的输赢比,并取相应的区间。

具体的计算已经由上面的函数完成,现在只要再包装一个函数来迭代地以不同的宽度呼出上面的函数,得到每个长度的最佳区间,再从这里面选出胜率最好的。
这里输入的statistics,tick_width,least_percentage和之前相同,另外的两个输入是
least_width – 最短的区间宽度
most_width – 最大的区间宽度
代码

# 返回一Series,内容 low=区间低点,high=区间高点,ratio=区间赢输比def find_absolute_best_region(statistics, tick_width, least_percentage, least_width, most_width):# 创建标注列的空DFcolumns = ['low', 'high', 'ratio']
    df = pd.DataFrame(columns = columns)# 对于所有在最短和最长标准之间的宽度for band_width in range(least_width, most_width   1):# 运行上面函数,得到该宽度的最佳区间best_width_region = find_best_region(statistics, tick_width, least_percentage, band_width)# 将结果加入DFdf = df.append(best_width_region, ignore_index = True)# 将赢输比从大到小排列sorted_table = df.sort('ratio', ascending = False)# 返回第一行return(sorted_table.iloc[0])

还是同样的例子,这次least_percentage选0.05,最小宽度least_width=2,最大宽度most_width=30。然后

find_absolute_best_region(stats, 0.01, 0.05, 2, 30)

得到宽度为19的区间[1.12,1.31],这之间的赢输比为1.72,这也是使用该指标时保证5%数据量的情况下的最大赢输比!

对于在策略中运用的补充

如果想把本篇统计算法的输出嵌入到交易策略中,应该注意以下几点:


不同股票要分开统计

因为每支股票的“个性”不同,所以对于不同指标的关联程度是不一样的,一支股票的最高胜率区间用在另一支股票上也许效果会截然不同,所以一定要把不同的股票分开统计。


每日更新统计数据

使用本方法的话最好把整个算法嵌入到策略中去,并且每日更新统计数据和最佳区间。如果不这么做的话,一则指标的最佳区间可能会过时,二则由于现在的最佳区间对于历史交易日来说是未来数据,所以以其进行回测会产生虚高的效果。


运用凯利公式
本篇的统计方法会计算出依照指标进行判断的成功率,利用该信息我们可以使用凯莉公式来优化投资收益。
凯莉公式点我,
具体的使用方法就留给大家当作业啦!


全部回复

0/140

量化课程

    移动端课程