TA 指标库
MACD 指数平滑异同平均线
KDJ 随机指标
RSI 强弱指标
ATR 平均真实波幅
OBV 能量潮
MA 移动平均线
EMA 指数平均数指标
BOLL 布林带
Alligator Alligator Indicator
CMF 蔡金货币流量指标
Highest 周期最高价
Lowest 周期最低价
拿MACD这个指标试着写一个测试,在这之前我们先展开API 文档上MACD标签,看下具体描述。
如果对于 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);
}
}
模拟盘测试结果:
可以看到,计算出来的指标开始时全部是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分钟, 因为要运行一段时间才能看到效果,所以选择小一点的周期。如图:
由图可见 发明者量化 机器人运行 计算出的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.
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 指标理解 正好大家可以看看指标都是怎么写出来的,我写的这个没有优化,效率很低,运行起来挺慢,权当学习吧。
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