和老白一起玩转JavaScript -- 创造一个会做买卖的小伙伴(2)诞生于沙盒

Author: 小小梦, Created: 2017-03-06 10:41:40, Updated: 2017-10-11 10:36:53

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

诞生于沙盒

  • 沙盒系统

    在网上搜索各种资料学习的时候发现用计算机程序去做金融证券交易就叫做程序化交易、量化交易。老白的数学水平也就大专级别,统计学更是只有一些基本概念:正态分布、期望、协整 等等。不敢说自己是搞量化的最多是在学习程序化、量化。在学习程序化、量化的过程中,或者实践中。沙盒系统是不可缺少的,这个就好比是一个沙盘游戏,里面有各种定义的资源、规则(有点像 minecraft的世界)。写出来的逻辑程序可以在这个沙盒系统里面各种测试,检验最基本的交易思路、逻辑、算法的正确性。

    老白感觉一个好用的沙盒需要具备以下几点:

    • 1、最大限度模拟真实的时间序列,即在沙盒中跑程序的时候时间序列要和真实情况尽可能的贴近,要基于tick级别,这样测试出的结果才有参考价值。可能会导致程序在沙盒中的运行速度有限,不过运行速度也是一个很重要的因素,总不能我测试一个程序要一天时间吧,这样就对沙盒系统要求比较高了。(尽可能真实而且又要速度快。)

    • 2、各种参数选项控制,这个要求的不是被测试程序的参数,而是沙盒系统的参数。比如:交易所的设定(期货?A股?外汇?)、测试中交易所的模拟账户信息、 交易所的手续费率、可能发生的滑点率、时间范围控制 等等。

    • 3、程序的参数调优:有时候老白有一大堆备用参数,想试试到底哪个好一点。好的沙盒系统可以接受一大堆预设参数,然后自己运行,分析出结果,显示出最好的。(这个是不是要求有点高…)

    • 4、容错测试: 程序模拟跑的时候往往都是一帆风顺、风平浪静,那是因为是在澡盆里面(能有多大浪?),而实际中可是望不到头的深海大洋,各种未知的暴风雨随时出现。那么沙盒系统就要在澡盆里面随机制造一些大风浪,最大程度模拟出恶劣的环境(就是放出各种错误数据,网络报错,甚至逻辑上不可能的数据。)

    • 5、图表显示:图表可以记录很多有用的数据用于分析,比如收益图表、差价曲线等等。

  • 正好手头上有一个沙盒系统,我们先用JS 写一些简单的代码 玩一玩。

    • 1、CTP商品期货 自动化程序的一般架构 老白使用的底层已经封装了一个函数 exchange.IO("status") 来识别与期货公司的前置服务器是否连接。这里跑题一下:期货公司的前置服务器?不是交易所么? 老白答: 商品期货使用的是CTP协议,连接结构是: 期货公司客户的终端程序(老白的代码)------>期货公司前置服务器------->交易所服务器 回到正题,在商品期货休市的时候,是无法连接到期货公司前置服务器的(休市一定时间后期货公司的前置服务器就关了)。或者一些情况导致的CTP断开连接。应对这些情况老白的程序就要间隔一定时间去判断连接状态,避免在未连接的情况下做一些操作,导致问题。

      function MainLoop(){  //  处理具体工作的函数
                          //  编写处理具体交易逻辑
      }
      function main() {
          var status = null;
          while(true){
              status = exchange.IO("status");      //  调用API 确定连接状态
              if(status === true){                 //  判断状态
                  LogStatus("已连接!");            //  在回测或者实际运行中显示一些实时数据、信息。
                  MainLoop();                      //  连接上 交易所服务器后,执行主要工作函数。
              }else{                               //  如果没有连接上 即 exchange.IO("status") 函数返回 false
                  LogStatus("未连接状态!");         //  显示 未连接状态。
              }
              Sleep(1000);                         //  封装的睡眠函数,参数是毫秒,1000毫秒 等于 1秒,需要有轮询间隔, 以免访问过于频繁。CTP协议是每秒推送2次数据。
          }
      }
      

      沙盒里面跑一下:

      img img

      看到图中,我们设置的回测账户资金了么,100W。

      img

    • 在程序中怎么获取自己的账户信息数据呢,由于封装了底层,做成了一个函数exchange.GetAccount() 来试试看就这个简单的一句。

      function MainLoop(){  //  处理具体工作的函数
          exchange.GetAccount();    
      }
      

      只在这个自定义的 MainLoop()函数中写入 exchange.GetAccount();

      结果什么都没显示。 哦!对了函数是运行了,但是没有调用日志打印函数。这个也是封装好的日志输出函数 Log() , 函数参数就是要输出的各种变量,可以传递多个用逗号间隔。(写代码的时候除了字符串里面可以用中文输入法输入,其余写代码的时候一定要记得切换英文,老白就掉过这个坑,浪费不少时间才发现是用中文输入法写符号了。)

      function MainLoop(){                     // 处理具体工作的函数
          Log(exchange.GetAccount());          // 写法1
          var Account = exchange.GetAccount(); // 写法2
      }
      

      img

      MainLoop 不停被执行(间隔1秒 全靠Sleep函数)所以回测系统日志全部输出的是 模拟账户信息。
    • 接下来在沙盒中请求一些别的数据,老白以前经常关注 螺纹钢 这个商品期货品种,因为同时也关注房价~嘿嘿。言归正题,既然要让程序替人做买卖,那么市场的行情是不可缺少的(老白去菜市场买菜可是要货比三家的哦!)。

      首先需要知道要了解哪个品种的行情,比如就 “螺纹钢1705合约” 吧,交易所中该合约编码: rb1705 (期货知识我也是百度自行补脑的), 就用 exchange.SetContractType("rb1705") 此外我想知道现在时刻这个 rb1705合约的 行情(回测系统中就是运行时的时间点)。 可以调用 exchange.GetTicker() 接着我想了解下该合约的历史价格周期统计。 函数为: exchange.GetRecords()MainLoop 函数做一下修改:

      var index = 0;                                                // 声明一个全局变量 用来记录循环次数
      function MainLoop(){
          var ContractInfo = exchange.SetContractType("rb1705");    // 设置我要操作的 商品期货合约类型 即 螺纹钢1705合约。
          if(!ContractInfo){
              return;                                               // 如果设置合约没有成功,即返回函数,再次进入重试。
          }
          Log("rb1705 Info:", ContractInfo);                        // 显示一下合约详细信息。
          var ticker = exchange.GetTicker();                        // 通过CTP协议请求 此刻行情数据
          var records = exchange.GetRecords();                      // 通过CTP协议请求 历史K线数据,K线的周期默认周期是在沙盒系统上设置的。
          Log("ticker:", ticker);                                   // 打印出来 此刻行情数据
          Log("records:", records);                                 // 打印出来 历史K线数据
          Log("index:", index++, "#FF0000");                        // 打印循环次数, 在最后参数传入 "#FF0000" 可以使打印的日志显示为红色。
      }
      

      img

      节选一部分records 变量的值(数组类型): [ {“Time”:1486083600000,“Open”:3354,“High”:3358,“Low”:3071,“Close”:3272,“Volume”:328708.00000000006},{“Time”:1486083900000,“Open”:3272,“High”:3272,“Low”:3228,“Close”:3228,“Volume”:133542}, …] Time : 时间戳,毫秒级时间。 Open : 开盘价 、High : 最高价 、 Low : 最低价 、 Close :收盘价 、 Volume : 成交量

      打印出的ticker 变量的值(对象): {“High”:3090.5,“Low”:3088.5,“Sell”:3090.5,“Buy”:3088.5,“Last”:3089.5,“Volume”:100} High : 当前最高价 、 Low : 当前最低价 、 Sell : 卖一价 、 Buy : 买一价 、 Last : 最后成交价 , Volume : 最近成交量

      rb1705合约的信息:(可以查看CTP协议中关于字段的描述。) { “CombinationType”:0, “CreateDate”:20160414, “DeliveryMonth”:5, “DeliveryYear”:1705, “EndDelivDate”:20170522, “ExchangeID”:“SHFE”, “ExchangeInstID”:“rb1705”, “ExpireDate”:20170515, “InstLifePhase”:49, “InstrumentID”:“rb1705”, “InstrumentName”:“rb1705”, “IsTrading”:1, “LongMarginRatio”:0.06, “MaxLimitOrderVolume”:500, “MaxMarginSideAlgorithm”:48, “MaxMarketOrderVolume”:30, “MinLimitOrderVolume”:1, “MinMarketOrderVolume”:1, “OpenDate”:20160517, “OptionsType”:0, “PositionDateType”:49, “PositionType”:50, “PriceTick”:1, “ProductClass”:49, “ProductID”:“rb”, “ShortMarginRatio”:0.06, “StartDelivDate”:20170516, “StrikePrice”:0, “UnderlyingInstrID”:"", “UnderlyingMultiple”:0, “VolumeMultiple”:10 }

    • 最后我们让机器人在沙盒里面活动一下

      这里介绍一点商品期货的概念,期货里面无论是买入多仓合约(看涨合约) 还是 买入空仓合约(看跌合约) 都叫开仓,为了区分:买入多仓合约叫开多仓,买入空仓合约叫开空仓。同样 平掉(对冲消除义务) 持有的多仓合约 和 平掉持有的空仓合约都叫平仓,为了区分:平掉多仓合约叫平多仓,平掉空仓合约叫平空仓。

平仓是指期货交易者买入或卖出与其所持期货合约的品种代码、数量及交割月份相同但交易方向相反的期货合约,了结头寸的行为。 期货交易者在最后交易日结束之前择机将买入的期货合约卖出,或将卖出的期货合约买回,是为了通过一笔数量相等、 方向相反的期货交易来冲销原有的期货合约,以此了结期货交易,解除到期进行实物交割的义务。 ```

##### 所以在期货市场做买卖就有4个方向:

用 ```SetDirection()``` 函数来 确定操作的方向

- 开多仓:SetDirection("buy") ,传入参数 "buy" 字符串,明确 exchange.Buy() 函数为 开多仓 操作, Buy 函数稍后讲到。

- 开空仓:SetDirection("sell"), 传入参数 "sell" 字符串,明确 exchange.Sell() 函数为 开空仓 操作,Sell 函数稍后讲到。

- 平多仓:SetDirection("closebuy"), 传入参数 "closebuy" 字符串, 明确 exchange.Sell()函数为 平多仓操作。

- 平空仓:SetDirection("closesell"), 传入参数 "closesell" 字符串,明确 exchange.Buy()函数为 平空仓操作。

下个单试试!继续改写 MainLoop 函数,我们让程序在沙盒里面每隔10分钟 交易一次,开多仓平多仓交替进行。
```
var index = 0;
var isFirst = true;
function MainLoop(){
    if(isFirst){
        Log(exchange.GetAccount());
        isFirst = false;
    }
    var ContractInfo = exchange.SetContractType("rb1705");
    if(!ContractInfo){
        return;                                               // 如果设置合约没有成功,即返回函数,再次进入重试。
    }
    var ticker = exchange.GetTicker();
    if(index % 2 === 0){
        exchange.SetDirection("buy");
        exchange.Buy(ticker.Last + 1, 1, ticker); // exchange.Buy 函数有2个必要参数,第一个参数为下单价格,
                                          // 第二个参数为下单数量(希望交易的数量),之后还可以跟一些参数输出在日志信息。 
                                          //ticker.Last + 1 是为了让单子能成交,意思是在最后成交价的基础上多出1块钱。
    }else if(index % 2 === 1){
        exchange.SetDirection("closebuy");
        exchange.Sell(ticker.Last - 1, 1, ticker); // ticker.Last - 1 是为了在最后成交价的基础上减去1元 卖出。
    }
    index++;
    Sleep(1000 * 60 * 10 - 1000);         // 这里暂停10分钟 ,减去的1000 即1秒是 main 函数循环中的1秒。
    Log(exchange.GetAccount());
}
```

![img](/upload/asset/bd6dd2ef0e5db88c70f0585aee3a417c92227d31.png) 

##### 开始的账户信息 和 最后一次开仓 前的账户信息比较,可见不能胡乱开仓平仓。 >_<

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

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

程序员 littleDream 原创


More