发明者量化学习日记(一)完结

Author: 小小梦, Created: 2016-05-23 23:10:46, Updated: 2019-08-01 10:31:25

以前很少写日志,现在感觉是需要每天记录点什么了,不能蹉跎光阴是吧!写点什么呢?就记录学习、工作吧!(其实都差不多。)今天除了日常工作,就是在给客户写一个模块,回测整个代码没有什么问题,除了图表上莫名其妙的少了一根指标线,其他都看似正常,OK,上模拟测试。各种各样的问题就来了。

问题1:

  • 1、图表K线有时会少一个BAR,正在找BUG。问题找到,分析了一下,是在用模拟盘测试的时候,没有接收到数据。 在接收到数据的时候,已经过了2个BAR了,所以最新的数据画在图表上了,导致中间隔过去了一个,或者多个。
  • 2、跑一段时间(1分钟K线周期的)没有问题,再试(30分钟K线周期)问题来了,莫名其妙的出现:

TypeError: cannot read property ‘length’ of null duk_hobject_props.c:2258 WToneAndWTtwo __FIL 这个报错。

看了下应该是在WToneAndWTtwo 这个函数中出的错,读取了null值的名字叫length这个属性,也就是 一个变量 比如 var name = null; 然后我 读取了name.length ,于是整片代码的找有调用length的地方。为了解决这个错误还写了个 函数:

function isNullAndLog(arr,strArrName){//空数组报错
     if(!arr){
         Log("arr is:",arr,"name:",strArrName);
         return true;
      }
      return false;
}

想,嘿嘿 !这下所有用到length的变量我都放进这个函数检测一下,有null的BUG绝对逃不了。改好代码,上机器人测试。 用的30分钟K线测试,擦~~竟然又 :

TypeError: cannot read property ‘length’ of null duk_hobject_props.c:2258 WToneAndWTtwo FILE:1315 报错

百思不得其解!!!耐心!出去转一圈换换脑子说不定就顿悟了!(Z大教我的)。溜了一圈,果真有效。 回来我就想到了,是不是调用的指标库 传入的参数是null了,指标库运行的时候出错了??动手实验,于是继续撸:

function isNullAndLog(arr,strArrName){//空数组报错
    if(!arr){
        Log("arr is:",arr,"name:",strArrName);
        return true;
    }
    return false;
}

function main(){
   if(typeof(x) === undefined ){
       Log(typeof(x));
   }
    var aaa = null;
    Log(isNullAndLog(aaa));
    /*
    while(!aaa || aaa.length < 9){
        Log("while xunhuan");
        Sleep(5000 * 1000);
    } 
    */
    talib.EMA(aaa,5);
} 

talib.EMA(aaa,5); 关键这句 aaa 是 null ,我故意设置的,传进去看看什么效果。 果然 报错了 TypeError: Cannot read property ‘length’ of null ~~OK 很可能就是这个问题了,继续修改代码。

问题2:

另外还有个问题是之前的想到了现在记录下代码:

function DeleteNullEle(initArr){
    var dealArr = [];
    var initArrLen = initArr.length;
    for(var i = 0,j = 0 ; i < initArrLen ; i++,j++){
    //Log("initArr-",i,":",initArr[i]);//ceshi
    if(initArr[i] === null || isNaN(initArr[i]) ){
        j--;
        //Log("i:",i);//ceshi
        continue;
    }
        dealArr[j] = initArr[i];
    }
    //Log("dealArr:",dealArr);//ceshi
return dealArr;
}

加入这个函数,原因是 有些数组 是这样的 [NaN,NaN,NaN,22,3,56,78,4,23] 但是在 发明者量化 测试 用Log 函数输出的时候显示的是null,这个 让我迷茫了好长时间啊,大坑,必须记着,你看到是null 说不定就是NaN ,所以判断null 是判断不出来的。
还有一点,就是 这个 while(!aaa || aaa.length < 10){…} ,如果 aaa === null 的话, 为什么不报错,因为 在 || 这个操作符的表达式 中!aaa 为真 表达式恒为真,所以后面的 aaa.length 可能就没执行(这点貌似在学习 HTML 、CSS 、DOM 的时候见过,判断游览器 的一些API用过。)

问题3:

另外应一个群友要求,帮修改了一下差价监控,其实就是加个检测差价范围,并且微信推送,很简单的。

 if (diff != __lastDiff) {
//--------------------------------------------------------------------------------------------
        if(openWeixin){
            if( (diff <= price1 || diff >= price2 ) && off_on === true ){
                Log("差价:"+diff+"微信推送@");
                off_on = false;
                Sleep(1000);
            }else{
                off_on = true;
                Sleep(1000);
                }
        }
//---------------------------------------------------------------------------------------------
        // add添加数据到series, 参数格式为[series序号, 数据];
        __chart.add([0, [new Date().getTime(), diff]]);
        __lastDiff = diff;
    }

img

在代码虚线之间的就是加入的代码,很简单,可能还不完善。

问题4(其实不是问题,一点学习记录)

这些天在学习期货的策略编写,这个比现货复杂点,(幸亏有Z大的期货交易模板,要不然写交易细节就够喝一壶了 >____< ! ),在学习期货的时候,首先要把基本的概念弄清楚, 比如:买开仓(做多),卖平仓(多头平仓),卖开仓(做空),买平仓(空头平仓)这些基本的要清楚。另外期货交易的API 也比现货的多且复杂点。盘点下也就API文档中的几个。

  • exchange.GetPosition() //用于获取持仓信息,注意这个函数返回的是一个position结构的数组,里面全是持仓的具体信息。
  • exchange.SetMarginLevel(MarginLevel) //这个函数就是 设置 杠杆的。具体的参数MarginLevel 选择(5倍?10倍?)看品种、交易所 ,具体而定,有的不能变(如CTP商品期货)
  • exchange.SetDirection(Direction) //这个是很重要的一个API ,这个在下单前调用,因为刚才介绍了4种操作(卖开仓…),这个函数就是设置这些操作的(不过还有点额外的内容),CTP的还有第二个参数暂时还没研究到。
  • exchange.SetContractType(ContractType) // 在开仓、平仓之前一个是要明确交易的方向(多?空?平?),另外一个是要明确交易的合约(就标的的是白糖?棉花?),所以就用这个函数设置合约类型。参数在不同的交易所具体而定。 基本也就用这些函数。剩下的函数就是和现货一样了。
  • exchange.Buy() // 买
  • exchange.Sell() // 卖 下面来看下我实验的代码,只节选了一段(可以复制在main函数里试试),回测 测试的交易所是 商品期货的交易所: img
 if (!manager) {//Z大写的商品期货模板类库,用来管理交易的对象(manager 初始时null)。不明白的可以去看源代码,QQ群共享有注释版。
        if (_C(exchange.GetPosition).length > 0) {
            throw "策略启动前不能有持仓.";
        }
        Log('交易平台:', exchange.GetName(), _C(exchange.GetAccount));  //输出当前交易所 信息
        var insDetail = _C(exchange.SetContractType, ContractTypeName);  // 用容错函数 _C ,容错调用 SetContractType函数  首先明确 设置交易的合约类型。
        Log("合约", insDetail.InstrumentName, "一手", insDetail.VolumeMultiple, "份, 最大下单量", insDetail.MaxLimitOrderVolume, "保证金率:", insDetail.LongMarginRatio.toFixed(4), insDetail.ShortMarginRatio.toFixed(4), "交割日期", insDetail.StartDelivDate);
        // 上面这个Log 显示出合约的一些信息,注意 在回测的时候,与实际运行的结果不一样,可以看图杠杆都是null,保证金率都是0,可以试试看。
        manager = $.NewPositionManager();  // 这个生成管理者 对象
    }
    exchange.SetMarginLevel(10); //不支持       在这里我试着设置了一下杠杆试试,发现  显示 错误:Not support     ,CTP商品期货 应该不能自己调整杠杆
    var positions = exchange.GetPosition();     在所有的操作前 ,我试着调用了一下 GetPosition 函数  获取一下持仓信息,显示的null
    Log(positions[0]);
    exchange.SetDirection("buy"); // 在所有的操作前需要设置  操作方向  ,这里设置 买开仓(做多)。
    var id = exchange.Buy(1911,1); // 执行买入操作  下了一个  限价单,  有兴趣的同学 可以试试 市价单。(嘿嘿!自己动手记得牢)
    positions = _C(exchange.GetPosition); //再获取下 持仓信息 看看
    Log(positions);  //有持仓信息了,  在上面 Buy的时候  有兴趣的同学 试试 用低价格 买入。  Buy(1000,1) 看看。

问题5:今天有位朋友写了段代码问,Sleep()函数 误差好大,影响高频策略。朋友发出了代码,我也跟着试了试。上代码:

function main(){
    for(var i = 0 ; i < 10 ; i++){
        var b1 = 0;
        var b2 = 0;
        var b3 = 15;
        while(b1 < 200){
            var t2s1 = (new Date()).getTime();
            b1++;
            Sleep(b3);
            var t2s6 = (new Date()).getTime();
            b2 += (t2s6 - t2s1);
        }
        Log("Sleep()",b3,"MS",b1,"平均次数",b2/b1);
    }
    throw 2;
}

我回测了一下,发现Sleep 函数 在回测里 会运行1秒(前后时间差为 1000),然后这段测试代码的时间误差很大,但是用模拟盘跑的话,基本上误差在1毫秒以内。可能是回测系统属于沙盒模型,里面时间的运行算法导致的误差。模拟就误差很小(1毫秒内)。但是朋友说虽然模拟误差小了,但是和我跑的还是不一样。我个人感觉是托管着得问题(我们测试的代码一样),我的托管着是本机运行的,他的是在VPS 上的托管着。感觉是这个问题,但是也说不清。回头请教Z大。

问题6: 今天一位朋友早上M我说半夜托管者崩溃了,发给我了系统日志。我报给Z大。问题找出,我这里记录下。

-这个是日志。

runtime/cgo: pthread_create failed: Resource temporarily unavailable
SIGABRT: abort
PC=0x7f332c591cc9 m=2

goroutine 0 [idle]:

goroutine 1 [select, 207 minutes]:

从第一行看到线程生成失败,资源暂时不可用(不足)。大概看出应该是内存的问题,可能是内存不足引起的。 解决办法: 1、检查运行的机器人策略代码,有没有使用的资源没有释放,有没有使用多线程的API。 2、使用ulimit 命令检查托管者所在系统的限制(Linux)。 这篇博客可以看下: (博客地址)[http://smilejay.com/2012/04/fork_resource/] 3、关注系统内存使用量。

使用 ulimit -a 显示: img

ulimit 命令的参数: -H 设置硬资源限制. -S 设置软资源限制. -a 显示当前所有的资源限制. -c size:设置core文件的最大值.单位:blocks -d size:设置数据段的最大值.单位:kbytes -f size:设置创建文件的最大值.单位:blocks -l size:设置在内存中锁定进程的最大值.单位:kbytes -m size:设置可以使用的常驻内存的最大值.单位:kbytes -n size:设置内核可以同时打开的文件描述符的最大值.单位:n -p size:设置管道缓冲区的最大值.单位:kbytes -s size:设置堆栈的最大值.单位:kbytes -t size:设置CPU使用时间的最大上限.单位:seconds -v size:设置虚拟内存的最大值.单位:kbytes -u <程序数目>  用户最多可开启的程序数目

遇到这个问题的朋友发现,错误过滤 函数也许是引起问题的原因,找到了导致托管者(内存不断膨胀)崩溃的原因了。原因是策略中 SetErrorFilter(“502:|503:|tcp|character|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF|reused”);函数 使用不当,该函数只需调用一次,不能写在循环结构中。上面的问题(内存不断膨胀)就是由于这个函数写在了onTick 函数中,onTick函数不断的循环调用,导致最终的崩溃。

问题7: 简单总结 _C()函数 , _N()函数 , _G()函数 的用法。

  • _C()函数

说明: 这个函数主要用于API容错,比如exchange.GetAccount()函数,这样调用: _C(exchange.GetAccount); 注意函数名不加括号,如果容错的函数需要加参数就写在_C()函数的第二个参数,依次往后。 使用例子。

function main(){
    var account = null;
    var amount = 1;
    var price = 2000;
    account = exchange.GetAccount();
    Log("_C 函数使用前  account :",account);
    //exchange = null; 
    _C(exchange.Buy,price,amount);
    //exchange.Buy(price,1);
    account = exchange.GetAccount();
    Log("_C 函数使用后  account :",account);
}

大概猜测_C 函数是这样的:

 function ___C(functionName,p1){
    functionName("执行需要容错的函数,传入的测试参数为:",p1);
    Log("__C 容错函数启动,容错完毕");
}
function main(){
    pstr = "测试参数--hello";
    ___C(Log,pstr);
}

执行结果: img

  • _N() 函数 说明: 这个函数 是为了 处理小数点后位数过多,保留若干位小数位数用的。 使用例子:
function main(){
    var pi = 3.1415926535897;
    Log("使用_N函数 前pi:",pi);
    var piOfDeal = _N(pi,2);
    Log("使用_N函数后 pi:",piOfDeal);
}

执行结果: img

  • _G()函数 说明:这个在API文档上有介绍,可保存的全局字典表。 KV表, 永久保存在本地文件, 每个机器人单独一个数据库, 重启或者托管者退出后一直存在 K必须为数字或者字符串, 不区分大小写, V可以为任何可以JSON序列化的内容 _G(“num”, 1); // 设置一个全局变量num, 值为1 _G(“num”, “ok”); // 更改一个全局变量num, 值为字符串ok _G(“num”, null); // 删除全局变量 num _G(“num”); // 返回全局变量num的值 _G(); // 返回当前机器人的ID _G(null); // 删除所有全局变量
function main(){
    Log("totalYLMoney 初始赋值 0");
    var totalYLMoney = 0;
    Log("totalYLMoney 已赋,  = ",totalYLMoney);
    if(typeof(_G("totalYLMoney_save")) !== "object"){
        totalYLMoney = _G("totalYLMoney_save"); 
        Log("读取 totalYLMoney 本地数据库 名称为totalYLMoney_save 的值 赋给 totalYLMoney ");
    }else{
        totalYLMoney = 100.12546328765458;
        _G("totalYLMoney_save",totalYLMoney.toString());
        Log("记录 totalYLMoney 保存到本地数据库");
    }
    Log("totalYLMoney",totalYLMoney);
    Log("typeof\(_G(\"totalYLMoney_save\"))",typeof(_G("totalYLMoney_save")));
}

上面的_G()函数测试代码需要在机器人上测试,回测不支持。测试方法,把程序部署在机器人上,第一次启动机器人, 机器人会记录100.12546328765458;这个值到全局字典"totalYLMoney_save"名称内,再次运行一次机器人,你会发现var totalYLMoney = 0,这个变量totalYLMoney初始化后,依然从字典中读取出了策略第一次运行时储存的值。

问题8: 关于策略 盈亏计算的一点心得:

很多时候看到群中的新同学,询问策略盈亏的算法。一般来说处理方法就几种,这里发下简单一点的,思路也是由群中大神提供,在这里我负责阐述一下。

  • 浮动利润:

浮动利润就是: 按 (现在币 - 初始币)x 现在的价格 + (现在的钱 - 初始的钱)

举例说明:比如 ,初始币价10元/个, 账户开始有币5个, 钱100元。 在一定时间段内 我以平均15元/个的价格 买了3个币。 在这段时间末,币价涨到20元/个。此刻我的账户为:币8个,钱55元。那么我们按照上边的方法计算一下此时间段内交易的盈亏(做多方向,做空方向原理相同)。 Profit = (8 - 5)* 20 + (55 - 100) = 60 + (-45) = 15 这种计算方法 盈利为 15元。 (注意:此刻你的收益是浮动的,因为如果此刻币价大跌,也许你不仅没利润了,还会赔钱,当然赔钱也是浮动的,因为你这个时候的收益是跟随币价浮动的,原因是做多没平仓,落袋) 补充点,如果在币价20元/个的时候,平仓,即20元卖出这均价15元买的3个币,此刻账户为:币5个,钱115元。那么还是上面的算法: Profit = (5 - 5) * 20 + (115 - 100) = 15 , 收益仍然是15元 ,区别是 若不考虑初始持币贬值,这样收益就不随价格变动了。

  • 账面利润:

账面利润 : (现在币 x 现在价格+现在钱) - (初始币 x 初始价格 + 初始钱)

还用上面的例子:比如 ,初始币价10元/个, 账户开始有币5个, 钱100元。 在一定时间段内 我以平均15元/个的价格 买了3个币。 在这段时间末,币价涨到20元/个。此刻我的账户为:币8个,钱55元。 Profit = (8 * 20 + 55) - (5 * 10 + 100) = 215 - 150 = 65 这种计算方法盈利是65元。同样我把均价15买来的3个币20元平仓卖出。此刻账户为:币5个,钱115,再次计算 Profit = (5 * 20 + 115) - (5 * 10 + 100) = 215 - 150 = 65 一样。

比较以上两种方法,一个收益15,一个收益65,其实不难看出。第一种方法忽略初始持币的贬值升值带来的盈亏。第二种算法则是计算盈亏时包括初始持币贬值升值产生的盈亏。 感觉账面利润的计算适用于结算资金 提钱离场。 浮动利润适用于在一段时间内通过交易产生的盈亏。(若干笔交易带来的盈亏) 这些是小小梦浅析的一些经验,如有错误,恳请大神留言指正,先言谢了 ^-^

问题9: 一位群友看到公开的一个策略的 状态栏 显示出了 表格,于是去翻阅了下API,发现貌似新增加了功能, API 中LogStatus函数 可以画表格。这位群友想请我测试测试,我也顺便记下,方便新同学学习。

先看看平台上的API 说明:

LogStatus(Msg); //	此信息不保存到日志列表里, 只更新当前机器人的状态信息, 在日志上方显示, 可多次调用, 更新状态
LogStatus('这是一个普通的状态提示');
LogStatus('这是一个红色字体的状态提示 #ff0000');
LogStatus('这是一个多行的状态信息\n我是第二行');
var table = {type: 'table', title: '持仓信息', cols: ['列1', '列2'], rows: [ ['abc', 'def'], ['ABC', 'support color #ff0000']]};
LogStatus('`' + JSON.stringify(table)+'`'); // JSON序列化后两边加上`字符, 视为一个复杂消息格式(当前支持表格)
LogStatus('第一行消息\n`' + JSON.stringify(table)+'`\n第三行消息'); // 表格信息也可以在多行中出现
LogStatus('`' + JSON.stringify([table, table])+'`'); // 支持多个表格同时显示, 将以TAB显示到一组里

测试代码:

function main(){
    var value1 = 99;
    var value2 ="ceshi 01";
    var table1 = { type : "table", title : "ceshi1", cols : ["列1","列2","列3"] , rows : [ ["abc","def","ghi"] , ["1","2","3"] ] };
    var table2 = { type : "table", title : "ceshi2", cols : ["列1","列2","列3"] , rows : [ ["abc",value1,"ghi"] , [value2,"2","3"] ] };
    LogStatus("测试文本1\n`" + JSON.stringify([table1,table2]) + "`");
}

img 第一张图可以看到 我们在 ceshi1 这个表格分页内,表格的数据格式就是 var table1 ,var table2 这两个声明过的变量。当然table1 表格内的内容全部都是固定的字符串。 需要注意的是 在 JSON.stringify([table1,table2]) 函数前后 要加上 “`” 这个。否则表格显示不出。 当然,我们用表格就要动态显示一些数据,总不能一直是固定的文本内容吧。 table2 这个分页内有2个测试数据, value1 、 value2 , 这两个数据。 img 新同学可以把代码复制下来,自己 试试,改改。这样记得比较牢固。

问题10: 一位用户留言想要一个收益率的图表,可能目前 发明者量化 的API文档叙述比较简单,图表使用上手可能比较困难。这里我写好了公开下,抛砖引玉。

直接可以测试,我用了随机数模拟动态变化的收益率,用来在图表上显示收益率曲线。上代码:

var ChartObj = {//画图
    tooltip: {xDateFormat: '%Y-%m-%d %H:%M:%S, %A',enabled:true,valueDecimals:2,valueSuffix:'%'}, //提示框  就是鼠标在线上时 显示的一个 框框 里面有一些内容,这里是设置提示框的格式
    chart: { zoomType:'x',panning:true },//图表缩放  
    title: { text: '收益率 %'}, //标题
    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: " 显示瞬时收益率 % "},//副标题
    xAxis:{type: 'datetime'}, //设置 X轴  的数值为时间类型
    yAxis: [{
            title: {text: '收益率 %'},//标题
            style: {color: '#4572A7'},//样式 
            //opposite: false  //生成右边Y轴
        }
    ],
    series: [//系列
        {name:'收益率',dataLables:{enabled:true,format:'{y} %'},type:'spline',yAxis:0,data:[]} //该图标只用到一条曲线  ,所以 只用设置一个数据序列
        ]                  
};
var chart = Chart(ChartObj);  //初始化

function main(){ //测试用的主函数
    chart.update(ChartObj); //更新
    chart.reset(); //清空图表
    var i = 0; //初始一个 i 值 , 后面循环用,   用来限制循环次数
    var randomNum = 0; //声明一个  随机数 初始化 0
    while(i < 5000){ //限制循环次数
        randomNum = Math.random() * 10;  // 生产随机数 0  -  9.9999
        //chart.add([0,[(new Date()),randomNum]]);
        chart.add(0,[(new Date()).getTime(),randomNum + 5]);  //把生成的随机数  按照时间(X 坐标)     把随机数 画在图表上(Y坐标)     
        Sleep(1000*60); // 程序 每次循环暂停60秒
        //chart.update(ChartObj);
        //chart.reset(500);
    }
    Log("over");
}
  • 这里一个问题让我迷惑了半天,也记录下。 就是在写入时间的时候 一定要是 毫秒数 ,这样写: new Date().getTime() 这样是毫秒数, 如果这样 new Date() 那么会出现一点小问题,比如时间显示, 还有X轴缩放不能使用。 还有tooltip: {xDateFormat: ‘%Y-%m-%d %H:%M:%S, %A’,enabled:true,valueDecimals:2,valueSuffix:’%’}, 这个设置 是查资料看到的。valueDecimals:2是限制显示的Y轴值保留2位小数,valueSuffix:’%'是在提示框显示的内容最后 加上 % 这个符号。

More

liuxinghui 小小梦老师,请教一下,下单后有未完成的订单要全部取消掉,应该怎么弄?

louis 这个必须赞一个!

凄凉雨 好东西....很多东西API文档并没有说出来..赞一个

叶落知秋 谢梦总整理的好东西

短线王赢出售高价策略 好东西!

小小梦 客气啦~ https://dn-filebox.qbox.me/bf52888dc68aba5326c403f2280994e81fbce2ee.png 教程 链接 : https://www.botvs.com/bbs-topic/475

小小梦 方便大家学习!嘿嘿!