2.7 指标的使用

Author: 小小梦, Created: 2016-11-10 16:19:36, Updated: 2019-08-01 09:25:06

指标的使用


  • TA - 优化重写部分常用指标函数库

TA 指标库
MACD       指数平滑异同平均线
KDJ        随机指标
RSI        强弱指标
ATR        平均真实波幅
OBV        能量潮
MA         移动平均线
EMA        指数平均数指标
BOLL       布林带
Alligator  Alligator Indicator
CMF        蔡金货币流量指标
Highest    周期最高价
Lowest     周期最低价

拿MACD这个指标试着写一个测试,在这之前我们先展开API 文档上MACD标签,看下具体描述。

img

如果对于 DIF 、 DEA 、指标算法等有兴趣的同学可以去百度搜索一下MACD算法,有很多资源,封装好的接口我们拿来即用。测试代码如下:

function main(){
    var records = null;
    var macd = null;
    while(true){
        records = _C(exchange.GetRecords);  // 获取K线数据 ,默认为策略界面设置的K线周期, _C 是一个容错的内置函数。
                                            // _C 详见 https://www.fmz.com/bbs-topic/320  问题7。
        macd = TA.MACD(records);   // 不加参数的话,使用的是默认参数  12, 26, 9
        Log("macd[0]", macd[0]);   // DIF
        Log("macd[1]", macd[1]);   // DEA
        Log("macd[2]", macd[2]);   // MACD 
        Log("macd[0]长度", macd[0].length);   // DIF 长度
        Log("macd[1]长度", macd[1].length);   // DEA 长度
        Log("macd[2]长度", macd[2].length);   // MACD 长度
        Log("records 长度:", records.length);  // 显示一下 records 的长度。
        Sleep(1000 * 60 * 5);
    }
}

模拟盘测试结果:

img

可以看到,计算出来的指标开始时全部是null , 后面的数据开始 有具体的数值了,这是因为指标参数规定的计算周期,在数据量(records 数据)不满足这个周期前无法计算出指标。 所以,在使用指标前要先了解指标的描述。并且在程序中判断用于计算指标的K线数据的长度。以免在长度不足的情况下计算出无效的值,并且使用无效的值,会引起程序错误。

下面我们通过使用图表接口让计算出来的指标显示在图表上,并且对比交易所的实盘图表(实盘选择 OKCoin ),先看代码:

var ChartCfg = {
tooltip: {xDateFormat: '%Y-%m-%d %H:%M:%S, %A'},
    chart: { zoomType:'x',panning:true },//图表类型  
    title: { text: "K-macd"}, //标题
    rangeSelector: {
            buttons:  [{type: 'hour',count: 1, text: '1h'}, {type: 'hour',count: 3, text: '3h'}, {type: 'hour', count: 8, text: '8h'}, {type: 'all',text: 'All'}],
            selected: 0,
            inputEnabled: false
        },
    subtitle: {text: "测试macd"},//副标题
    xAxis:{type: 'datetime'},
    yAxis: [{
            title: {text: 'K线'},//标题
            style: {color: '#4572A7'},//样式 
            opposite: false  //生成右边Y轴
        },
       {
            title:{text: "macd"},
            opposite: true  //生成右边Y轴  ceshi
       }
    ],
    series: [//系列
        {type:'candlestick',yAxis:0,name:'K',id:'KLine',data:[]},
        {name:"DIF",type:'spline',yAxis:1,data:[]},
        {name:"DEA",type:'spline',yAxis:1,data:[]},
        {name:"MACD量柱",type:'spline',yAxis:1,data:[]},
        ]                  
};
function main(){
    var records = null;
    var macd = null;
    var perRecords = _C(exchange.GetRecords);
    var perRecordTime = perRecords[perRecords.length - 1].Time;
    var chart_obj = Chart(ChartCfg); // 初始化图表
    chart_obj.reset();
    while(true){
        records = _C(exchange.GetRecords);  // 获取K线数据 ,默认为策略界面设置的K线周期, _C 是一个容错的内置函数。
        if(!records && records.length < 26 ){
            continue;
        }
        macd = TA.MACD(records, 12, 26, 9);   // 不加参数的话,使用的是默认参数  12, 26, 9
        if(records[records.length - 1].Time !== perRecordTime){                                    // _C 详见 https://www.fmz.com/bbs-topic/320  问题7。
            //先更新,再添加K线
            chart_obj.add(0, [records[records.length - 2].Time, records[records.length - 2].Open, records[records.length - 2].High, records[records.length - 2].Low, records[records.length - 2].Close], -1);   // 跟新刚完成的bar。
            chart_obj.add(0, [records[records.length - 1].Time, records[records.length - 1].Open, records[records.length - 1].High, records[records.length - 1].Low, records[records.length - 1].Close]);       // 添加新出现的bar
            //先更新,添加指标线
            chart_obj.add(1, [records[records.length - 2].Time, macd[0][records.length - 2]], -1);   // 更新
            chart_obj.add(1, [records[records.length - 1].Time, macd[0][records.length - 1]]);
            
            chart_obj.add(2, [records[records.length - 2].Time, macd[1][records.length - 2]], -1);   // 更新
            chart_obj.add(2, [records[records.length - 1].Time, macd[1][records.length - 1]]);
            
            chart_obj.add(3, [records[records.length - 2].Time, macd[2][records.length - 2]], -1);   // 更新
            chart_obj.add(3, [records[records.length - 1].Time, macd[2][records.length - 1]]);
            
            perRecordTime = records[records.length - 1].Time;
        }else{
            //只更新当前的bar 和 线
            chart_obj.add(0, [records[records.length - 1].Time, records[records.length - 1].Open, records[records.length - 1].High, records[records.length - 1].Low, records[records.length - 1].Close], -1);
            chart_obj.add(1, [records[records.length - 1].Time, macd[0][records.length - 1]], -1);   // 更新
            chart_obj.add(2, [records[records.length - 1].Time, macd[1][records.length - 1]], -1);   // 更新
            chart_obj.add(3, [records[records.length - 1].Time, macd[2][records.length - 1]], -1);   // 更新
        }
        chart_obj.update(ChartCfg);
        Sleep(1000);
    }
}

机器人界面上的K线周期参数设置为1分钟, 因为要运行一段时间才能看到效果,所以选择小一点的周期。如图:

img img

由图可见 发明者量化 机器人运行 计算出的DIF 约为 2.729 , DEA 约为 2.646 MACD量柱 约为 0.0831 实际OKCoin 交易所 行情图表 显示的 DIF 为 2.73 , DEA 为 2.65 MACD量柱 为 0.17 可以看到,前两个DIF 、DEA 差别很小,OKCoin 做了四舍五入, MACD 差一倍, 是因为 OKCoin 是这样算的: (DIF - DEA) * 2 , 通常是 DIF - DEA = 2.729 - 2.646 = 0.083, 如果 再乘以2 就是 0.166 约等于0.17.

其它指标使用方法基本上类似。

  • talib - http://ta-lib.org/

    除了TA 库常用的指标, 在talib 库还有很多指标。 举个例子我们试着使用一下 talib.STOCHRSI 这个指标,看下API文档描述:
STOCHRSI	Stochastic Relative Strength Index
STOCHRSI(Records[Close],Time Period = 14,Fast-K Period = 5,Fast-D Period = 3,Fast-D MA = 0) = [Array(outFastK),Array(outFastD)]

可以看到,参数设置是 talib.STOCHRSI(records, 14, 14, 3, 3); 基本上还是上边MACD 类似的代码,改动了一些,跑一下看看。

var ChartCfg = {
tooltip: {xDateFormat: '%Y-%m-%d %H:%M:%S, %A'},
    chart: { zoomType:'x',panning:true },//图表类型  
    title: { text: "stochrsi"}, //标题
    rangeSelector: {
            buttons:  [{type: 'hour',count: 1, text: '1h'}, {type: 'hour',count: 3, text: '3h'}, {type: 'hour', count: 8, text: '8h'}, {type: 'all',text: 'All'}],
            selected: 0,
            inputEnabled: false
        },
    subtitle: {text: "测试stochrsi"},//副标题
    xAxis:{type: 'datetime'},
    yAxis: [{
            title: {text: 'K线'},//标题
            style: {color: '#4572A7'},//样式 
            opposite: false  //生成右边Y轴
        },
       {
            title:{text: "K-D"},
            opposite: true  //生成右边Y轴  ceshi
       }
    ],
    series: [//系列
        {type:'candlestick',yAxis:0,name:'K',id:'KLine',data:[]},
        {name:"K",type:'line',yAxis:1,data:[]},
        {name:"D",type:'line',yAxis:1,data:[]},
        ]                  
};
function main(){
    var records = null;
    //var macd = null;
    var stochrsi = null;
    var perRecords = _C(exchange.GetRecords);
    var perRecordTime = perRecords[perRecords.length - 1].Time;
    var chart_obj = Chart(ChartCfg); // 初始化图表
    chart_obj.reset();
    while(true){
        records = _C(exchange.GetRecords);  // 获取K线数据 ,默认为策略界面设置的K线周期, _C 是一个容错的内置函数。
        if(!records && records.length < 26 ){
            continue;
        }
        //macd = TA.MACD(records, 12, 26, 9);   // 不加参数的话,使用的是默认参数  12, 26, 9
        stochrsi = talib.STOCHRSI(records, 14, 5, 3, 0);
        if(records[records.length - 1].Time !== perRecordTime){                                    // _C 详见 https://www.fmz.com/bbs-topic/320  问题7。
            //添加K线
            chart_obj.add(0, [records[records.length - 2].Time, records[records.length - 2].Open, records[records.length - 2].High, records[records.length - 2].Low, records[records.length - 2].Close], -1);   // 跟新刚完成的bar。
            chart_obj.add(0, [records[records.length - 1].Time, records[records.length - 1].Open, records[records.length - 1].High, records[records.length - 1].Low, records[records.length - 1].Close]);       // 添加新出现的bar
            //添加指标线
            chart_obj.add(1, [records[records.length - 2].Time, stochrsi[0][records.length - 2]], -1);   // 更新
            chart_obj.add(1, [records[records.length - 1].Time, stochrsi[0][records.length - 1]]);
            
            chart_obj.add(2, [records[records.length - 2].Time, stochrsi[1][records.length - 2]], -1);   // 更新
            chart_obj.add(2, [records[records.length - 1].Time, stochrsi[1][records.length - 1]]);
            
            perRecordTime = records[records.length - 1].Time;
        }else{
            //只更新当前的bar 和 线
            chart_obj.add(0, [records[records.length - 1].Time, records[records.length - 1].Open, records[records.length - 1].High, records[records.length - 1].Low, records[records.length - 1].Close], -1);
            chart_obj.add(1, [records[records.length - 1].Time, stochrsi[0][records.length - 1]], -1);   // 更新
            chart_obj.add(2, [records[records.length - 1].Time, stochrsi[1][records.length - 1]], -1);   // 更新
        }
        chart_obj.update(ChartCfg);
        LogStatus("倒数第一组数据:", stochrsi[0][stochrsi[0].length - 1], stochrsi[1][stochrsi[1].length - 1], "    倒数第二组数据:", stochrsi[0][stochrsi[0].length - 2], stochrsi[1][stochrsi[1].length - 2]);
        Sleep(1000);
    }
}

篇幅有限我就不截图显示了,总之是和OKCoin官方的数值有差别,BOSS说talib库的实现算法可能和OKCoin的不一样。我在网上扒拉了一圈只找到了一点点资料,于是有了另外一篇帖子: STOCHRSI 指标理解 正好大家可以看看指标都是怎么写出来的,我写的这个没有优化,效率很低,运行起来挺慢,权当学习吧。


More

hokshelato 您说“最后一根 K 线需要先更新再添加,因为最后一根 K 线是一直在变动的,只有当新的 K 线生成以后,前一根 K 线才是确定的。” 我不是很理解,通过 `GetRecords()` 获得的 K 线数据难道不是确定的吗?`records[length -1]` 代表的是当前时间戳下的 K 线数据,直接画到图表中即可,等有了新的时间戳的 K 线数据,再添加到图表的最后一个元素即可,不是吗?望不吝赐教。

shandianliyu 请问有没有关于 TA.ATR(records, 14) 更详细的说明,我在BotVS的API文档,视频以及完全使用手册中都没有找到。在我调用 TA.ATR(records, 14) 时(参数为30分钟k线),返回的是一个长度为 177 的数组。请问这个数组的含义是什么?

FangBei python版本 https://dn-filebox.qbox.me/b5d2b0ecc1e196a6bfc68eb45cd818c50d279915.png https://dn-filebox.qbox.me/157db5e2659fd13bcf552cd82fe456ba469939f8.png

FangBei python版本 def main(): while true: records = _C(exchange.GetRecords); # 获取K线数据 ,默认为策略界面设置的K线周期, _C 是一个容错的内置函数。 macd = TA.MACD(records); # 不加参数的话,使用的是默认参数 12, 26, 9 Log("macd[0]", macd[0]); # DIF Log("macd[1]", macd[1]); # DEA Log("macd[2]", macd[2]); # MACD Log("macd[0]长度", len(macd[0])); # DIF 长度 Log("macd[1]长度", len(macd[1])); # DEA 长度 Log("macd[2]长度", len(macd[2])); # MACD 长度 Log("records 长度:", len(records)); # 显示一下 records 的长度。 Sleep(1000 * 60 * 5);

小小梦 K线Bar 的时间戳是 这个Bar 的起始时间, 并不是 实时 时间, Bar 的起始时间是不会变动的。

hokshelato 嗯,我之前理解的误区在于 K 线的**时间戳**上。调试下来后发现 BotVS 的机制是这样的,以您说的**日 K 线**为例: 如果我要获取 3月1日 这一天的 K 线数据,调用 `GetRecords(D1)` 会返回很多条记录,但它们的**时间戳都是一样的**,都是`2018-03-01 00:00:00`。 也就是说,即使我在 3月1日 10点 调用了 `GetRecords(D1)`,获取到的 K 线时间戳,依然是 2018-03-01 **00**:00:00。所以才有了您说的最后一根是会不确定的,直到有了新的时间戳,即 2018-03-**02** 00:00:00,则 3月1日这一天的时间戳便确定了。 我此前理解的误区在于,我以为我在 3月1日 1点、2点、3点调用后返回的时间是不同的,即 2018-03-01 **01**:00:00、2018-03-01 **02**:00:00、2018-03-01 **03**:00:00。

小小梦 GetRecords() 获取的 K线数据 除了 倒数第一根 ,其余是确定的(因为周期已经完成),倒数第一根 不确定是因为 周期没有走完,Close 一直在变动。比如 日K 线, 当前的日K 线柱,在今天没有过完的时候, 谁也不知道 今天最终会多少价格 收盘。

小小梦 不客气 ^^

shandianliyu 明白了,非常感谢!

小小梦 是的 对应 倒数第一根 K线 Bar。

shandianliyu 好的,非常感谢您的回答。最后再确认一下,var a = TA.ATR(records, 14), a[a.length-1] 这个表示的是最近k线数据的ATR吧?

小小梦 是的 具体 返回多少是 看交易所 的,并不能 指定。所以 交易所 一般 提供的都是 有限 数据。

shandianliyu 这个我明白,TA.ATR()实质就是对传入的records进行了一个数学运算。是我之前没有表达清楚,我想问的是用函数exchange.GetRecords() 获取k线数据时,返回的长度是由交易所决定的吗?

小小梦 这个 指标函数 返回多少 数据 是和 K线数据 也就是 records 对应的, 你传入的 records 数组 长度是多少 , 算出来的 长度 就是多少。

shandianliyu 不好意思,我还是不太明白,我的意思是,如果调用这个函数,那么返回数据的长度是交易所给定的吗?我发现不同时刻调用这个函数,即使参数相同,返回数据的长度也可能不同。

小小梦 是由 K线长度 决定的 ^^

shandianliyu 感谢您的回答,这下清楚了,另外想问一下,这个117的长度是由什么决定的?

小小梦 举例 传入的records 为 日K线, 计算出的 ATR 就是 一个 数组, 这个数组的 倒数第一个元素 就是 当天的(日K线 最后一根的日期)ATR 指标。 依次类推 30分钟 K线 , 15分钟K线。 您算出的 177 的数组 就是 177 个K线周期 ,对应每个 K线Bar 的 ATR指标。

小小梦 举个栗子~ 如果 现在K线数据只有10个Bar 的时候,我要求 MA(15) 即 15周期均线, 那么 肯定是求不出来的,因为只有10个周期,无法计算15个周期的平均值。

FangBei 另外,我用实盘级tick回测,没有数据返回

FangBei 前面的数据为什么会不满足周期要求呢?

小小梦 这个例子挺好, 就是在实际使用中 ,最新的指标数据都是在数组 最后的, 就是 索引 -1 的元素。前边不满足周期要求 计算出的数据都是 None, 后面才有 有效数据,如图: https://dn-filebox.qbox.me/b7837ea30e5d8396ffa91c48204f2fbc9a7f4504.png