更新于20171114 a. 解决Open找不到问题,是record数组访问越界造成的,访问越界是之前的K线有问题导致的。 更新于20171113 a. 过滤掉起始时间不正确的K线组合 b. 过滤掉时间间隔不正确的K线组合 更新于20170622 a. RecordsManager 增加Name参数,便于区分不同的K线 b. Fix 当固定K线数目不满一根新K线周期时,Time计算不正确的问题
更新于20170531 a. fix Volume计算错误
原理:
功能:
限制:
输出函数: $.RecordsManager(NewCycleMS, Name) 生成新周期管理器 NewCycleMS 为新K线周期毫秒数. 默认(10006060*2) 2hour. Name: 为该K线管理指定名字 返回K线管理器 $.AssembleRecords(records, BaseCycleMS) records: 拿到的原始records BaseCycleMS 为固定K线周期毫秒,默认用固定records进行计算 返回新K线周期的records $.GetRecordsTable(n) 得到新K线最新的N个条目, 默认输出所有条目, 输出为table类型,便于LogStatus输出 $.Get***** 获得一些基本信息
/*backtest period: 60 */ /* 20180627 修改了天无法整合的bug 20180118 屏蔽掉一些log输出 更新于20171114 a. 解决Open找不到问题,是record数组访问越界造成的,访问越界是之前的K线有问题导致的。 更新于20171113 a. 过滤掉起始时间不正确的k线组合 b. 过滤掉时间间隔不正确的k线组合 更新于20170622 a. RecordsManager 增加Name参数,便于区分不同的K线 b. Fix 当固定K线数目不满一根新K线周期时,Time计算不正确的问题 更新于20170531 a. fix Volume计算错误 1. 修改自小小梦的"转换任意K线周期" 模板 原理: - 获得固定K线的周期,然后合成任意固定K线整数倍的新K线周期 功能: - 转换基础K线为任意K线周期 - 暂时不支持 秒级别 限制: - 新K线周期必须是固定K线周期的整数倍. - 固定K线周期为1min, 3min, 5min, 15min, 30min, 新K线周期也必须是分钟且<60 - 固定K线周期为1hour, 新K线周期也必须是小时且<24 - 固定K线周期为1day, 新K线周期也必须是天 - 每次获得的固定K线周期数目必须>=2 测试版本,如有BUG ,问题 欢迎留言。 输出函数: $.RecordsManager(NewCycleMS, Name) 生成新周期管理器 NewCycleMS 为新K线周期毫秒数. 默认(1000*60*60*2) 2hour. Name: 为该K线管理指定名字 返回K线管理器 $.AssembleRecords(records, BaseCycleMS) records: 拿到的原始records BaseCycleMS 为固定K线周期毫秒,默认用固定records进行计算 返回新K线周期的records $.GetRecordsTable(n) 得到新K线最新的N个条目, 默认输出所有条目, 输出为table类型,便于LogStatus输出 $.Get***** 获得一些基本信息 */ function EasyReadTime(millseconds) { if (typeof millseconds == 'undefined' || !millseconds) { millseconds = new Date().getTime(); } var newDate = new Date(); newDate.setTime(millseconds); return newDate.toLocaleString(); } var cloneObj = function(obj) { // 深拷贝 对象函数 var str, newobj = obj.constructor === Array ? [] : {}; if (typeof obj !== 'object') { return; } else if (JSON) { str = JSON.stringify(obj); //系列化对象 newobj = JSON.parse(str); //还原 } else { for (var i in obj) { newobj[i] = typeof obj[i] === 'object' ? cloneObj(obj[i]) : obj[i]; } } return newobj; }; var DAY = 0; var HOURS = 1; var MINUTES = 2; function GetDHM(objTime, BaseCycle, NewCycleForMS){ var ret = []; if(BaseCycle % (1000 * 60 * 60 * 24) === 0){ ret[0] = objTime.getDate(); ret[1] = DAY; }else if(BaseCycle % (1000 * 60 * 60) === 0){ ret[0] = objTime.getHours(); ret[1] = HOURS; }else if(BaseCycle % (1000 * 60) === 0){ ret[0] = objTime.getMinutes(); ret[1] = MINUTES; } if(NewCycleForMS % (1000 * 60 * 60 * 24) === 0){ ret[2] = DAY; }else if(NewCycleForMS % (1000 * 60 * 60) === 0){ ret[2] = HOURS; }else if(NewCycleForMS % (1000 * 60) === 0){ ret[2] = MINUTES; } return ret; } function SearchFirstTime(ret, BaseCycle, NewCycleForMS){ if(ret[1] === DAY && ret[2] === DAY){ var array_day = []; for(var i = 1 ; i < 29; i += (NewCycleForMS / BaseCycle)){ array_day.push(i); } for(var j = 0 ; j < array_day.length; j++ ){ if(ret[0] === array_day[j]){ return true; } } }else if(ret[1] === HOURS && ret[2] === HOURS){ var array_hours = []; for(var i = 0 ; i < 24; i += (NewCycleForMS / BaseCycle)){ array_hours.push(i); } for(var j = 0 ; j < array_hours.length ; j++){ if(ret[0] === array_hours[j]){ return true; } } }else if(ret[1] === MINUTES && ret[2] === MINUTES){ var array_minutes = []; for(var i = 0; i < 60; i += (NewCycleForMS / BaseCycle)){ array_minutes.push(i); } for(var j = 0; j < array_minutes.length; j++){ if(ret[0] === array_minutes[j]){ return true; } } }else{ throw "目标周期与基础周期不匹配!目标周期毫秒数:" + NewCycleForMS + " 基础周期毫秒数: " + BaseCycle; } } function Calc_High(AssRecords, n, BaseCycle, NewCycleForMS){ var max = AssRecords[n].High; for(var i = 1 ; i < NewCycleForMS / BaseCycle; i++){ max = Math.max(AssRecords[n + i].High, max); } return max; } function Calc_Low(AssRecords, n, BaseCycle, NewCycleForMS){ var min = AssRecords[n].Low; for(var i = 1 ; i < NewCycleForMS / BaseCycle; i++){ min = Math.min(AssRecords[n + i].Low, min); } return min; } function _RecordsManager(NewCycleForMS, Name) { if (typeof NewCycleForMS == 'string') { this._NewCycleForMS = 1; var arrayNum = NewCycleForMS.split("*"); for(var indexNum = 0 ; indexNum < arrayNum.length ; indexNum++){ this._NewCycleForMS = this._NewCycleForMS * Number(arrayNum[indexNum]); } } else { this._NewCycleForMS = NewCycleForMS; } this._Name = ""; if (Name) { this._Name = Name; } this._Records = new Array(); this.GetNewCycleForMS = function() { return this._NewCycleForMS; }; this.GetRecords = function() { return this._Records; } this.AssembleRecords = function(records, BaseCycle) { var NewCycleForMS = this._NewCycleForMS; var AssRecords = records.slice(0); // 深拷贝 var AfterAssRecords = []; if (!records || records.length == 0) { Log("record 为空@!"); return records; } if(records.length < 2){ throw (!records) ? "传入的records参数为 错误" + records : "基础K线长度小于2"; } if (typeof BaseCycle === 'undefined') { BaseCycle = records[records.length - 1].Time - records[records.length - 2].Time; } if(NewCycleForMS % BaseCycle !== 0){ //Log(EasyReadTime(records[records.length - 1].Time), EasyReadTime(records[records.length - 2].Time)); //Log("目标周期‘", NewCycleForMS, "’不是 基础周期 ‘", BaseCycle, "’ 的整倍数,无法合成!"); return null; } if(NewCycleForMS / BaseCycle > records.length){ Log("records: ", records, "NewCycleForMS: ", NewCycleForMS, ", BaseCycle: ", BaseCycle); throw "基础K线数量不足,请检查是否基础K线周期过小!"; } // 判断时间戳, 找到 基础K线 相对于 目标K线的起始时间。 var objTime = new Date(); var isFirstFind = true; var FirstStamp = null; for (var i = 0; i < AssRecords.length; i++) { objTime.setTime(AssRecords[i].Time); var ret = GetDHM(objTime, BaseCycle, NewCycleForMS); if (isFirstFind === true && SearchFirstTime(ret, BaseCycle, NewCycleForMS) === true) { FirstStamp = AssRecords[i].Time; for (j = 0; j < i; j++) { AssRecords.shift(); // 把目标K线周期前不满足合成的数据排除。 } isFirstFind = false; break; // 排除后跳出 }else if(isFirstFind === false){ if((AssRecords[i].Time - FirstStamp) % NewCycleForMS === 0){ for (j = 0; j < i; j++) { AssRecords.shift(); // 把目标K线周期前不满足合成的数据排除。 } break; } } } var BarObj = { // 定义一个 K线柱结构 Time: 0, Open: 0, High: 0, Low: 0, Close: 0, Volume: 0, }; var n = 0; for (n = 0; n < AssRecords.length - (NewCycleForMS / BaseCycle);) { // 合成 /* { Time :一个时间戳, 精确到毫秒,与Javascript的 new Date().getTime() 得到的结果格式一样 Open :开盘价 High :最高价 Low :最低价 Close :收盘价 Volume :交易量 } */ //时间判断 var is_bad = false; var start_time = AssRecords[n].Time; var stop_time = AssRecords[n + (NewCycleForMS / BaseCycle) - 1].Time + BaseCycle; if (ret[2] != DAY && start_time % NewCycleForMS != 0) { //Log("过滤起始时间不正确的k线组合", EasyReadTime(start_time)); is_bad = true; } if (stop_time - start_time != NewCycleForMS) { //Log("过滤时间间隔不正确的k线组合", EasyReadTime(start_time), EasyReadTime(stop_time)); is_bad=true; } if (is_bad) { n++; continue; } BarObj.Time = AssRecords[n].Time; BarObj.Open = AssRecords[n].Open; BarObj.High = Calc_High(AssRecords, n, BaseCycle, NewCycleForMS); BarObj.Low = Calc_Low(AssRecords, n, BaseCycle, NewCycleForMS); BarObj.Close = AssRecords[n + (NewCycleForMS / BaseCycle) - 1].Close; BarObj.Volume = 0; for (var j = n; j < n + (NewCycleForMS / BaseCycle); j++) { BarObj.Volume += AssRecords[j].Volume; } AfterAssRecords.push(cloneObj(BarObj)); n += (NewCycleForMS / BaseCycle) } if (n == 0) { BarObj.Time = AssRecords[0].Time; } else { BarObj.Time = AssRecords[n - (NewCycleForMS / BaseCycle)].Time + NewCycleForMS; // 最后一根时间不能变, } BarObj.Open = AssRecords[n].Open; BarObj.Close = AssRecords[AssRecords.length - 1].Close; BarObj.Volume = AssRecords[n].Volume; //BarObj.Volume = 0; var max = AssRecords[n].High; var min = AssRecords[n].Low; for(var index_n = n + 1 ;index_n < AssRecords.length; index_n++){ max = Math.max(max, AssRecords[index_n].High); min = Math.min(min, AssRecords[index_n].Low); BarObj.Volume += AssRecords[index_n].Volume; } BarObj.High = max; BarObj.Low = min; AfterAssRecords.push(cloneObj(BarObj)); this._Records = AfterAssRecords; return AfterAssRecords; }; this.GetKlineName = function () { return " " + this._NewCycleForMS / 60 / 1000 + " 分钟K线"; }; /* 获得records数据表格*/ this.GetRecordsTable = function (n) { if (typeof n !== 'undefined' && n >=0 ) { var records = this._Records.slice(-n); } else { var records = this._Records.slice(0); } var record_array = new Array(); for (var i = records.length - 1; i >= 0; i--) { var newDate = new Date(); newDate.setTime(records[i].Time); var time_str = newDate.toLocaleString(); record_array.push([time_str, records[i].Open, records[i].Close, records[i].High, records[i].Low, records[i].Volume]); } var title = this._Name + " " + this.GetKlineName() + "(" + records.length + "根)"; var table = {type: 'table', title: title, cols: ['Time', 'Open','Close', 'High', 'Low', 'Volume'], rows: record_array}; return table; } } $.RecordsManager = function (NewCycleForMS, Name) { if (typeof NewCycleForMS === 'undefined') { NewCycleForMS = UI_NewCycleForMS; } var RecordsManager = new _RecordsManager(NewCycleForMS, Name); return RecordsManager; } function main() { var records = exchange.GetRecords(); while (!records || records.length < 24) { records = exchange.GetRecords(); Sleep(1000); } while (true) { records = _C(exchange.GetRecords); record_manager0 = $.RecordsManager(UI_NewCycleForMS, "Hello World"); new_records0 = record_manager0.AssembleRecords(records); var table0 = record_manager0.GetRecordsTable(); var BaseCycle = records[records.length - 1].Time - records[records.length - 2].Time; record_manager1 = $.RecordsManager(BaseCycle); new_records1 = record_manager1.AssembleRecords(records); var table1 = record_manager1.GetRecordsTable(); LogStatus('`' + JSON.stringify([table0, table1, ""]) +'`'); records = record_manager1.GetRecords(); //Log(records[records.length-1]); Sleep(60000); } }
zerozaki 得到的K线时间不对啊 大神看一下 4小时周期 得到的数据是: ``` 2018-07-30 19:00:00.000+08:00 8162.42 8187.69 8200 8156.84 681.53875929 2018-07-30 12:00:00.000+08:00 8192.91 8185.3 8209.35 8151 902.81758958 2018-07-30 08:00:00.000+08:00 8213.11 8154.49 8225.18 8051 899.96507317 2018-07-30 04:00:00.000+08:00 8214.25 8191.52 8229.08 8190.03 879.26269857 2018-07-30 00:00:00.000+08:00 8234.88 8185.32 8247.36 8170.04 817.78392428 ``` 7天周期 等到的数据是: ``` 2018-08-05 08:00:00.000+08:00 8213.11 8186.16 8278.23 8051 7869.96734215 2018-07-29 08:00:00.000+08:00 8235.84 8213.63 8301.2 8116.8 20943.43495 2018-07-28 08:00:00.000+08:00 8187.47 8236.39 8245.23 8070 21465.51463651 2018-07-27 08:00:00.000+08:00 7930.92 8188.83 8278 7800.44 22985.16836742 2018-07-26 08:00:00.000+08:00 8171.44 7936.88 8316.49 7850.88 22644.64610719 2018-07-25 08:00:00.000+08:00 8404.91 8171.44 8485 8050 22036.92873654 2018-07-24 08:00:00.000+08:00 7715.53 8404.95 8498.04 7695.05 23595.19928712 2018-07-23 08:00:00.000+08:00 7395.57 7716.12 7813 7370.26 27205.92883481 2018-07-22 08:00:00.000+08:00 7402.1 7395.14 7585.15 7330.64 26186.61248972 2018-07-21 08:00:00.000+08:00 7326.6 7404 7450 7210 28545.21532011 2018-07-20 08:00:00.000+08:00 7472.61 7326.59 7691.67 7265.14 28703.79798543 ``` 代码: ``` function main() { // 5分钟 const period_M5 = 1000*60*5 // 4小时 const period_H4 = 1000*60*60*4 // 周线 const period_D7 = 1000*60*60*24*7 const periods = [ period_M5, period_H4, period_D7, ] while (true) { const tables = periods.map(period=>{ let records = null if (period>period_D1) { records = exchange.GetRecords(PERIOD_D1) Log("日线级别", records.length) } else if (period>period_H1) { records = exchange.GetRecords(PERIOD_H1) Log("小时线级别", records.length) } else { records = exchange.GetRecords(PERIOD_M1) Log("分钟线级别", records.length) } const recordManager = $.RecordsManager(period) const newRecords = recordManager.AssembleRecords(records, period) const table = recordManager.GetRecordsTable() return table }) LogStatus(`\`${JSON.stringify(tables)}\``) Sleep(60*1000) } } ```
guigui17f 211行,如果把 isFirstFind 声明在函数内部的话后面else那部分可以去掉,因为每次 isFirstFind 都是true,每次进的都是第一个分支,可以打Log看一下。
xiaoxiaolu TypeError: cannot read property 'Time' of undefined at main (__FILE__:525) 基本上运行一天就出现这样的报错停止,生成的K线有时候找不到Time
中本姜 const newRecords = recordManager.AssembleRecords(records, period) 这一句用错了吧? 不应该是period 应该可以不写 (不写的话,程序会用records算K线间隔),或者写上records中K线的时间间隔毫秒数
中本姜 const newRecords = recordManager.AssembleRecords(records, period) 这一句用错了吧? 不应该是period 应该可以不写,或者写上records中K线的时间间隔毫秒数
zrcahyx 我自己确认过了,是回测开始时间太早导致的,建议可以在代码里Log一下
guigui17f 应该是为了避免重复计算?但是放外面的话如果中途有一段时间没有获取新数据,是不是会导致计算错误呢?感觉还是每次算一下的保险~~
中本姜 能给一下RecordsManager() 以及AseembleRecords() 中给的参数是什么吗,我可以debug一下。
中本姜 的确如你所说,我还是没能理解小小梦当时的逻辑,我再研究研究改改。
zrcahyx main:98:14 - TypeError: Cannot read property 'length' of null / main:242:57 TypeError: cannot read property 'Time' of undefined 同样会出现这个问题
中本姜 哦,好吧,我有时间看看code改改, 如果再出现问题了,把原K线和新K线说一下,我看看能不能重现
xiaoxiaolu 不清楚啊 代码一多 都不知道问题在哪里 但是小梦梦的原版合成模板可以正常使用
中本姜 你的固有K线和新K线设置的什么?我一直用着,跑几个月没问题啊