和老白一起玩转JavaScript -- 创造一个会做买卖的小伙伴(5) 机器人也生虫(BUG)

Author: 小小梦, Created: 2017-03-13 12:53:50, Updated: 2017-10-11 10:37:32

和老白一起玩转JavaScript – 创造一个会做买卖的小伙伴(5)

使用JS 编写量化机器人的一个教程: https://www.fmz.com/bbs-topic/705 (先 Mark 下)。
  • 1、 K线的 数据基础知识 、 量化交易中使用这些数据出现的问题。

    • 什么是K线数据:

      K线图这种图表源处于日本德川幕府时代,被当时日本米市的商人用来记录米市的行情与价格波动,后因其细腻独到的标画方式而被引入到股市及期货市场。目前,这种图表分析法在我国以至整个东南亚地区均尤为流行。由于用这种方法绘制出来的图表形状颇似一根根蜡烛,加上这些蜡烛有黑白之分,因而也叫阴阳线图表。通过K线图,我们能够把每日或某一周期的市况表现完全记录下来,股价经过一段时间的盘档后,在图上即形成一种特殊区域或形态,不同的形态显示出不同意义。我们可以从这些形态的变化中摸索出一些有规律的东西出来。K线图形态可分为反转形态、整理形态及缺口和趋向线等。 (查询自百度)

      具体图形不解释了,我们看下 使用JS 语言定义的K线的数据结构:

      {
          Time    :   1487034000000, // 一个时间戳, 精确到毫秒,与Javascript的 new Date().getTime() 得到的结果格式一样
          Open    :   3425,          // 开盘价
          High    :   3446,          // 最高价
          Low     :   3423,          // 最低价
          Close   :   3438,          // 收盘价
          Volume  :   177657.99,     // 交易量
      }
      

      我们再看下调用 GetRecords 函数获取的数据: (记得先调用 exchange.SetContractType(“rb1705”); 明确要操作的合约类型)

      [
          {"Time":1487034000000,"Open":3425,"High":3446,"Low":3423,"Close":3438,"Volume":177657.9999999999},
          {"Time":1487035800000,"Open":3438,"High":3448,"Low":3382,"Close":3385,"Volume":494882},
          {"Time":1487037600000,"Open":3385,"High":3398,"Low":3383,"Close":3394,"Volume":83656.00000000015}
      ]
      

      可见K线数据都是一个对象数组,每个对象就是一个 K线 Bar ,包含 这个Bar 周期内的 最高价格、 最低价格、 开盘价格(即这个K线时间戳开始时的价格)、收盘价格(即这个K线周期时间结束时的价格)、成交量(周期内的成交量)。而这个周期 就是 K线周期。 比如上面的数组中的数据怎么判断是多大周期的K线呢? 可以用2根Bar 的时间戳只差计算: 1487035800000 - 1487034000000 得出: 1800000 , 这个数值的单位是 毫秒,所以换算下: 1800000 / 1000 / 60 = 30 (分钟) , 这个K线周期就是30分钟。

      第一个容易出现的问题: 使用K线数据时,忽略了 数组长度。导致数组访问溢出(这类BUG在以前写C程序的时候很常见)。所以我们在使用K线前需要对其判断 比如获取K线: exchange.SetContractType(“rb1705”); // 切换设置 为 螺纹钢 1705 合约。 var records = exchange.GetRecords(); // 获取 rb1705 螺纹钢 合约的 默认K线周期数据。 具体可以获取到多少根Bar 的K线数据 是具体看 交易所的 API 推送的。所以在开始的时候如果需要比较多的K线Bar,必须让程序收集一段时间。并且在程序里面要增加判断,是否收集足够,可以这样写:

      if(records.length < n){    // n 就是我们限定的 n线数量。
          return;                // 当前函数返回。
      }
      

      第二个容易出现的问题: K线最后一个Bar 的数据,除了 Time 属性、 Open属性 以外,其他的属性都是有可能变化, Close 属性更是实时在变动。 初学者在 处理K线的时候由于不明白这点会有很多困惑。比如上一章节,讲到均线交叉。使用倒数第一Bar 还是倒数第二Bar ?

      第三个问题: K线的周期,时间戳就是这个周期的起始时刻,时间戳是一个 毫秒数, 值为0 的时间戳 代表的时刻是 1970年1月1日 (具体写程序的时候判断还需要考虑所在时区)。 可以用以下这一句在 w3school 或者 BotVS沙盒系统中 测试下:

      var arr = new Date(0);
      

      显示为:

      Thu Jan 01 1970 08:00:00 GMT+0800 (CST) // 显示的为东八时区 这个值是从1970年自己累加到现在了(每过1秒增加1000, 因为1秒是1000毫秒),所以这个数值已经是比较大了。 这里有个小技巧:时间戳由于是确定K线周期的K线中每个Bar唯一的,所以一旦时间戳发生变化,那么就可以确定接收到最新的K线数据了。这个在实际处理K线数据中也是很有用的。

  • 2、 指标调用详解,经常遇到的问题 满足周期 、 返回值 、参数

    在程序化或者量化策略编写时,也会用到不少指标函数, 比较好用的指标库有 talib 库,这个有各种版本,我们这里用到JS版本。

    在老白第一次使用指标库的函数时也出过不少错误:

    • 第一,参数周期(指标参数, 区别于K线周期,K线周期为多少,计算出来的指标K线周期也就是多少,比如30分钟K线计算出来的就是30分钟周期的 MACD 指标,参数为参数周期)设置过大,K线数据长度不足: 比如MACD指标,描述:

      MACD(Records[Close],Fast Period = 12,Slow Period = 26,Signal Period = 9) = [Array(outMACD),Array(outMACDSignal),Array(outMACDHist)]
      

      在使用时如果参数周期设置 12,26,9, 我们传入用于计算指标的K线数据records, 代码这样写:

      var macd = talib.MACD(records, 12, 26, 9);
      

      如果此时传入的 records 数据的长度过小。计算出来的就是这样的:

      [
        [null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null],
        [null,null,null,null,null,null,null,null,null,null,null,null,null]
      ]
      

      这个就是由于K线数据不足引起的,计算出的指标如果使用就会引起BUG,所以我们在程序前加个限定条件:

      while(!records || records.length < 50){
          records = exchange.GetRecords();
          Sleep(1000);
      }
      

      直到获取足够50根K线,才跳出循环。执行如下: img

      有细心的读者可以看到,为什么这个指标计算出的数据是一个二维数组(即 一个数组的每个元素又是一个数组),因为MACD指标计算出来的并不是一条线,而是三条线分别为: dif 、dea 、macd量柱。所以每个指标的返回值可能都不一样,具体还是需要看下指标描述。

      [
        [null,null,null,null,null,null,null,null,数据...],
        [null,null,null,null,null,null,null,null,数据...],
        [null,null,null,null,null,null,null,null,数据...]
      ]
      

      有几次也是因为没注意指标的返回结构,出现BUG。

    • 第二,指标函数计算使用的均线不一样或者指标算法不一样导致结果不同。

      比较明显的是 STOCH RSI 这个指标,描述如下:

      STOCHRSI(Records[Close],Time Period = 14,Fast-K Period = 5,Fast-D Period = 3,Fast-D MA = 0) = [Array(outFastK),Array(outFastD)]
      

      这个计算出来的值明显不同于其他的算法,这个指标在本系列的第一章中给出了我自己的算法代码。有兴趣的读者可以对比下。 原因有可能是使用的均线系统不一致导致的,有些库的算法习惯使用MA、有些习惯用EMA。 部分指标都是每天迭代计算出来的,如果K线数据给定的数量不同可能算出的值有差别。

  • 3、 API 容错处理

    • Cannot read property ‘length’ of null 这个BUG 是 出现频率最高的,没有之一。

      原因就是API有时由于各种原因会发生获取数据错误、或者没有获取到数据。这时一些获取数据的API取到的就是null值这些数据一般都是数组结构,经常需要访问数组的长度。

      对于所有的API 调用都需要 进行容错处理,甚至有些时候需要检测一下数据是否正常(有时会出现异常数据)。我们的程序只能保证在自身代码内的准确性,但是对于网络上奔跑穿梭的数据信息是无法保证其100%准确的(丢包什么的不可避免),于是就必须对获取的数据容错处理,过滤掉所有的异常数据。

      由于没有单步调试、没有断点调试、没有变量值监控 等等~ 我平时DEBUG的方法就是最简单的 Log 大法! 对于程序流程中合理使用Log 输出文本信息,分析程序输出的日志。可以大概了解程序的运行过程,也可以结合 try , catch , throw JS的异常捕获来处理BUG,但是我的建议是不到必须使用异常捕获的时候,尽量不要用。(程序化要求我们必须处理交易、分析数据的各个方面,万万不能勉强运行,可能灾难性的BUG就在某个角落等待触发)。 对于DEBUG来说,用最原始的Log大法确实是个经验活,从培养DEBUG能力的角度来说这样是很有效的。积累到一定程度,无惧任何BUG。

先写到这,欢迎读者给我留言!提出建议和意见,如果感觉好玩可以分享给更多热爱程序热爱交易的朋友

https://www.fmz.com/bbs-topic/728

程序员 littleDream 原创


More