summerXXX 发表于 2017-3-22 13:04

多品种海龟 期货 源码 翻译版

### CTP商品期货多品种海龟交易策略 (注释版)




--------------------------------------------------------------------

代码逐行翻译了一边,适合量化程序化初学者。

##### 听说看注释前, 熟悉一下海龟交易法,效果会更好哦!

```
/*
参数:
Instruments             合约列表                  字符串(string)      MA701,CF701,zn1701,SR701,pp1701,l1701,hc1610,ni1701,i1701,v1701,rb1610,jm1701,ag1612,al1701,jd1701,cs1701,p1701
LoopInterval            轮询周期(秒)            数字型(number)       3
RiskRatio               % Risk Per N ( 0 - 100) 数字型(number)       1
ATRLength               ATR计算周期               数字型(number)      20
EnterPeriodA            系统一入市周期             数字型(number)      20
LeavePeriodA            系统一离市周期             数字型(number)      10
EnterPeriodB            系统二入市周期             数字型(number)      55
LeavePeriodB            系统二离市周期             数字型(number)      20
UseEnterFilter          使用入市过滤            布尔型(true/false)   true
IncSpace                加仓间隔(N的倍数)          数字型(number)      0.5
StopLossRatio         止损系数(N的倍数)          数字型(number)      2
MaxLots               单品种加仓次数             数字型(number)      4
RMode                   进度恢复模式            下拉框(selected)   自动|手动
VMStatus@RMode==1       手动恢复字符串             字符串(string)      {}
WXPush                  推送交易信息            布尔型(true/false)   true
MaxTaskRetry            开仓最多重试次数         数字型(number)       5
KeepRatio               预留保证金比例             数字型(number)      10
*/

var _bot = $.NewPositionManager();                                                          // 调用CTP商品期货交易类库 的导出函数 生成一个用于单个品种交易的对象
// 源码模块地址 : 百度BotVS
var TTManager = {                                                                           // 海龟策略 控制器
    New: function(needRestore, symbol, keepBalance, riskRatio, atrLen, enterPeriodA, leavePeriodA, enterPeriodB, leavePeriodB, useFilter,
      multiplierN, multiplierS, maxLots) {
      // 该控制器对象 TTManager 的属性 New 赋值一个 匿名函数(构造海龟的函数,即:构造函数),用于创建 海龟任务,参数分别是:
      // needRestore: 是否需要恢复,symbol:合约代码,keepBalance:必要的预留的资金,riskRatio:风险系数, atrLen:ATR指标(参数)周期。enterPeriodA:入市周期A
      // leavePeriodA:离市周期A , enterPeriodB:入市周期B, leavePeriodB:离市周期B,useFilter:使用过滤,multiplierN:加仓系数,multiplierS:止损系数,maxLots:最大加仓次数

      // subscribe
      var symbolDetail = _C(exchange.SetContractType, symbol);                           
      // 声明一个局部变量 symbolDetail 用于接受API SetContractType 函数的返回值(值为symbol的合约的详细信息,symbol 是 "MA709",返回的就是甲醇709合约的详细信息),
      // 调用API SetContractType 订阅并切换合约为 symbol 变量值的合约。 _C() 函数的作用是 对 SetContractType 合约容错处理,即如果 SetContractType返回null 会循环重试。
      if (symbolDetail.VolumeMultiple == 0 || symbolDetail.MaxLimitOrderVolume == 0 || symbolDetail.MinLimitOrderVolume == 0 || symbolDetail.LongMarginRatio == 0 || symbolDetail.ShortMarginRatio == 0) {
      // 如果 返回的合约信息对象symbolDetail 中 VolumeMultiple、MaxLimitOrderVolume 等数据异常,则调用 throw 抛出错误,终止程序。
            Log(symbolDetail);
            throw "合约信息异常";
      } else {                                                                           // 检索的数据没有异常则,输出部分合约信息。
            Log("合约", symbolDetail.InstrumentName, "一手", symbolDetail.VolumeMultiple, "份, 最大下单量", symbolDetail.MaxLimitOrderVolume, "保证金率:", _N(symbolDetail.LongMarginRatio), _N(symbolDetail.ShortMarginRatio), "交割日期", symbolDetail.StartDelivDate);
      }

      var ACT_IDLE = 0;                                                                  // 定义一些宏 (标记)
      var ACT_LONG = 1;
      var ACT_SHORT = 2;
      var ACT_COVER = 3;                                                                   // 动作宏


      var ERR_SUCCESS = 0;                                                               // 错误宏
      var ERR_SET_SYMBOL = 1;
      var ERR_GET_ORDERS = 2;
      var ERR_GET_POS = 3;
      var ERR_TRADE = 4;
      var ERR_GET_DEPTH = 5;
      var ERR_NOT_TRADING = 6;
      var errMsg = ["成功", "切换合约失败", "获取订单失败", "获取持仓失败", "交易下单失败", "获取深度失败", "不在交易时间"];// 错误宏的值 对应该数组的索引,对应索引的值就是翻译

      var obj = {                              // 声明一个对象,构造完成后返回。单个的海龟策略控制对象。
            symbol: symbol,                        // 合约代码         构造函数执行时的参数传入
            keepBalance: keepBalance,            // 预留的资金       构造函数执行时的参数传入
            riskRatio: riskRatio,                  // 风险系数         构造函数执行时的参数传入
            atrLen: atrLen,                        // ATR 长度         构造函数执行时的参数传入
            enterPeriodA: enterPeriodA,            // 入市周期A      构造函数执行时的参数传入
            leavePeriodA: leavePeriodA,            // 离市周期A      构造函数执行时的参数传入
            enterPeriodB: enterPeriodB,            // 入市周期B      构造函数执行时的参数传入
            leavePeriodB: leavePeriodB,            // 离市周期B      构造函数执行时的参数传入
            useFilter: useFilter,                  // 使用入市过滤条件构造函数执行时的参数传入
            multiplierN: multiplierN,            // 加仓系数 基于N   构造函数执行时的参数传入
            multiplierS: multiplierS               // 止损系数 基于N   构造函数执行时的参数传入
      };
      obj.task = {                               // 给 obj对象添加一个 task 属性(值也是一个对象),用来保存 海龟的任务状态数据。
            action: ACT_IDLE,                      // 执行动作
            amount: 0,                           // 操作量
            dealAmount: 0,                         // 已经处理的操作量
            avgPrice: 0,                           // 成交均价
            preCost: 0,                            // 前一次交易成交的额度
            preAmount: 0,                        // 前一次成交的量
            init: false,                           // 是否初始化
            retry: 0,                              // 重试次数
            desc: "空闲",                           // 描述信息
            onFinish: null                         // 处理完成时的 回调函数,即可以自行设定一个 回调函数在完成当前 action 记录的任务后执行的代码。
      }
      obj.maxLots = maxLots;                     // 赋值 最大加仓次数构造函数执行时的参数传入
      obj.lastPrice = 0;                         // 最近成交价,用于计算 持仓盈亏。
      obj.symbolDetail = symbolDetail;         // 储存 合约的详细信息 到obj 对象的 symbolDetail 属性
      obj.status = {                           // 状态数据
            symbol: symbol,                        // 合约代码
            recordsLen: 0,                         // K线长度
            vm: [],                              // 持仓状态 , 用来储存 每个品种的 ,手动恢复字符串。
            open: 0,                               // 开仓次数
            cover: 0,                              // 平仓次数
            st: 0,                                 // 止损平仓次数
            marketPosition: 0,                     // 加仓次数
            lastPrice: 0,                        // 最近成交价价格
            holdPrice: 0,                        // 持仓均价
            holdAmount: 0,                         // 持仓数量
            holdProfit: 0,                         // 浮动持仓盈亏
            N: 0,                                  // N值 ,即ATR
            upLine: 0,                           // 上线
            downLine: 0,                           // 下线
            symbolDetail: symbolDetail,            // 合约详细信息
            lastErr: "",                           // 上次错误
            lastErrTime: "",                     // 上次错误时间信息
            stopPrice: '',                         // 止损价格
            leavePrice: '',                        //
            isTrading: false                     // 是否在交易时间
      };

      obj.setLastError = function(err) {         // 给obj对象添加方法,设置 最近一次的错误信息
            if (typeof(err) === 'undefined' || err === '') {                                     // 如果参数未传入,或者 错误信息为 空字符串
                obj.status.lastErr = "";                                                         // 清空 obj 对象的 status 属性的 对象的lastErr属性
                obj.status.lastErrTime = "";                                                   // 清空
                return;                                                                        // 返回
            }
            var t = new Date();                                                                  // 获取新时间
            obj.status.lastErr = err;                                                            // 设置错误信息
            obj.status.lastErrTime = t.toLocaleString();                                       // toLocaleString()    根据本地时间格式,把 Date 对象转换为字符串。
      };
      obj.reset = function(marketPosition, openPrice, N, leavePeriod, preBreakoutFailure) {    // 给obj对象添加方法,恢复仓位。
            // 参数,marketPosition:加仓次数,openPrice:最后一次加仓价, N:N值, leavePeriod:离市周期,preBreakoutFailure:是否上次突破失败
            if (typeof(marketPosition) !== 'undefined') {                                        // 如果 第一个参数不是未定义 ,传入参数
                obj.marketPosition = marketPosition;                                             // 给obj 添加属性 marketPosition : 加仓次数 正数为多仓,负数为空仓
                obj.openPrice = openPrice;                                                       // 最后一次加仓价
                obj.preBreakoutFailure = preBreakoutFailure;                                     // 是否上次突破失败
                obj.N = N;                                                                     // N值
                obj.leavePeriod = leavePeriod;                                                   // 离市周期
                var pos = _bot.GetPosition(obj.symbol, marketPosition > 0 ? PD_LONG : PD_SHORT); // 调用 模板类库生成的 交易控制对象的成员函数GetPosition 获取 持仓信息
                if (pos) {                                                                     // 如果获取到持仓信息
                  obj.holdPrice = pos.Price;                                                   // 根据获取的持仓信息 给obj 属性赋值
                  obj.holdAmount = pos.Amount;                                                 // 同上
                  Log(obj.symbol, "仓位", pos);                                                 // 输出显示当前仓位
                } else {                                                                         // 如果GetPosition 返回null ,没有找到持仓信息。
                  throw "恢复" + obj.symbol + "的持仓状态出错, 没有找到仓位信息";                  // 抛出异常
                }
                Log("恢复", obj.symbol, "加仓次数", obj.marketPosition, "持仓均价:", obj.holdPrice, "持仓数量:", obj.holdAmount, "最后一次加仓价", obj.openPrice, "N值", obj.N, "离市周期:", leavePeriod, "上次突破:", obj.preBreakoutFailure ? "失败" : "成功");
                // 输出恢复的 相关参数,数据。
                obj.status.open = 1;                                                            // 设置 开仓 计数为1
                obj.status.vm = ;// 储存 手动恢复字符串 数据。
            } else {                                                                              // 没有传入参数,即不恢复, 全部初始化。
                obj.marketPosition = 0;                                                         // 初始化各项变量
                obj.holdPrice = 0;
                obj.openPrice = 0;
                obj.holdAmount = 0;
                obj.holdProfit = 0;
                obj.preBreakoutFailure = true; // test system A                                 // 此处设置true会使策略 尝试 突破系统A
                obj.N = 0;
                obj.leavePeriod = leavePeriodA;                                                   // 用系统A 的离市周期 赋值
            }
            obj.holdProfit = 0;                                                                   // 初始化
            obj.lastErr = "";
            obj.lastErrTime = "";
      };

      obj.Status = function() {                                                               // 给Obj 添加 Status 函数, 把Obj 的一些属性值 赋值给 Obj.status 同样意义的属性
            obj.status.N = obj.N;                                                               // 给 obj.status 赋值
            obj.status.marketPosition = obj.marketPosition;
            obj.status.holdPrice = obj.holdPrice;
            obj.status.holdAmount = obj.holdAmount;
            obj.status.lastPrice = obj.lastPrice;
            if (obj.lastPrice > 0 && obj.holdAmount > 0 && obj.marketPosition !== 0) {            // 如果有持仓
                obj.status.holdProfit = _N((obj.lastPrice - obj.holdPrice) * obj.holdAmount * symbolDetail.VolumeMultiple, 4) * (obj.marketPosition > 0 ? 1 : -1);
                // 计算持仓盈亏 = (最近成交价 - 持仓价格)* 持仓量 * 一手合约份数 , 计算出来 保留4位小数, 用 obj.marketPosition(加仓次数) 属性的 正负 去修正,计算结果的正负(做空按照这个算法是相反的负数,所以要用-1修正)。
            } else {
                // 如果没有持仓,浮动盈亏赋值为0
                obj.status.holdProfit = 0;
            }
            return obj.status;                                                                  // 返回这个 obj.status 对象(用于显示在界面状态栏?)
      };
      obj.setTask = function(action, amount, onFinish) {                                        // 给obj 对象添加 方法,设置任务
            // 参数,action:执行动作,amount:数量,onFinish: 回调函数
            obj.task.init = false;                                                                // 重置 初次执行标记 为false
            obj.task.retry = 0;                                                                   // 重置..
            obj.task.action = action;                                                             // 参数传来的 动作指令 赋值
            obj.task.preAmount = 0;                                                               // 重置
            obj.task.preCost = 0;
            obj.task.amount = typeof(amount) === 'number' ? amount : 0;                           // 如果没传入参数 ,设置 0
            obj.task.onFinish = onFinish;
            if (action == ACT_IDLE) {                                                             // 如果 动作指令是 空闲
                obj.task.desc = "空闲";                                                            // 描述变量赋值为“空闲”
                obj.task.onFinish = null;                                                         // 赋值为 null
            } else {                                                                               // 其他动作
                if (action !== ACT_COVER) {                                                      // 如果不等于 平仓动作
                  obj.task.desc = (action == ACT_LONG ? "加多仓" : "加空仓") + "(" + amount + ")"; // 根据 action 设置描述 信息
                } else {                                                                           // 如果是平仓 动作 设置描述信息为 “平仓”
                  obj.task.desc = "平仓";
                }
                Log("接收到任务", obj.symbol, obj.task.desc);                                        // 输出日志 显示 接收到任务。
                // process immediately
                obj.Poll(true);                                                                  // 调用 obj 对象的方法 处理 任务,参数是 true , 参数为true ,控制Poll 只执行 一部分(子过程)
            }
      };


summerXXX 发表于 2017-3-22 13:05

obj.processTask = function() {                                                            // 处理 交易任务
            var insDetail = exchange.SetContractType(obj.symbol);                                 // 切换 要操作的合约
            if (!insDetail) {                                                                     // 切换失败 返回错误
                return ERR_SET_SYMBOL;
            }
            var SlideTick = 1;                                                                      // 滑价设置为1 个 PriceTick
            var ret = false;                                                                        // 声明返回值初始false
            if (obj.task.action == ACT_COVER) {                                                   // 处理 指令为全平的 任务,这部分处理 类似 商品期货交易类库 不再赘述,可以参见 商品期货交易类库注释版
                var hasPosition = false;
                do {
                  if (!$.IsTrading(obj.symbol)) {
                        return ERR_NOT_TRADING;
                  }
                  hasPosition = false;
                  var positions = exchange.GetPosition();
                  if (!positions) {
                        return ERR_GET_POS;
                  }
                  var depth = exchange.GetDepth();
                  if (!depth) {
                        return ERR_GET_DEPTH;
                  }
                  var orderId = null;
                  for (var i = 0; i < positions.length; i++) {
                        if (positions.ContractType !== obj.symbol) {
                            continue;
                        }
                        var amount = Math.min(insDetail.MaxLimitOrderVolume, positions.Amount);
                        if (positions.Type == PD_LONG || positions.Type == PD_LONG_YD) {
                            exchange.SetDirection(positions.Type == PD_LONG ? "closebuy_today" : "closebuy");
                            orderId = exchange.Sell(_N(depth.Bids.Price - (insDetail.PriceTick * SlideTick), 2), Math.min(amount, depth.Bids.Amount), obj.symbol, positions.Type == PD_LONG ? "平今" : "平昨", 'Bid', depth.Bids);
                            hasPosition = true;
                        } else if (positions.Type == PD_SHORT || positions.Type == PD_SHORT_YD) {
                            exchange.SetDirection(positions.Type == PD_SHORT ? "closesell_today" : "closesell");
                            orderId = exchange.Buy(_N(depth.Asks.Price + (insDetail.PriceTick * SlideTick), 2), Math.min(amount, depth.Asks.Amount), obj.symbol, positions.Type == PD_SHORT ? "平今" : "平昨", 'Ask', depth.Asks);
                            hasPosition = true;
                        }
                  }
                  if (hasPosition) {
                        if (!orderId) {
                            return ERR_TRADE;
                        }
                        Sleep(1000);
                        while (true) {
                            // Wait order, not retry
                            var orders = exchange.GetOrders();
                            if (!orders) {
                              return ERR_GET_ORDERS;
                            }
                            if (orders.length == 0) {
                              break;
                            }
                            for (var i = 0; i < orders.length; i++) {
                              exchange.CancelOrder(orders.Id);
                              Sleep(500);
                            }
                        }
                  }
                } while (hasPosition);
                ret = true;
            } else if (obj.task.action == ACT_LONG || obj.task.action == ACT_SHORT) {                   // 处理 建/加多仓 任务或者处理 建/加空仓 任务,这部分处理 类似 商品期货交易类库 不再赘述,可以参见 商品期货交易类库注释版。(此策略没有使用商品期货交易类库的交易功能,在次直接植入了处理代码)
                do {
                  if (!$.IsTrading(obj.symbol)) {
                        return ERR_NOT_TRADING;
                  }
                  Sleep(1000);
                  while (true) {
                        // Wait order, not retry
                        var orders = exchange.GetOrders();
                        if (!orders) {
                            return ERR_GET_ORDERS;
                        }
                        if (orders.length == 0) {
                            break;
                        }
                        for (var i = 0; i < orders.length; i++) {
                            exchange.CancelOrder(orders.Id);
                            Sleep(500);
                        }
                  }
                  var positions = exchange.GetPosition();
                  // Error
                  if (!positions) {
                        return ERR_GET_POS;
                  }
                  // search position
                  var pos = null;
                  for (var i = 0; i < positions.length; i++) {
                        if (positions.ContractType == obj.symbol && (((positions.Type == PD_LONG || positions.Type == PD_LONG_YD) && obj.task.action == ACT_LONG) || ((positions.Type == PD_SHORT || positions.Type == PD_SHORT_YD) && obj.task.action == ACT_SHORT))) {
                            if (!pos) {
                              pos = positions;
                              pos.Cost = positions.Price * positions.Amount;
                            } else {
                              pos.Amount += positions.Amount;
                              pos.Profit += positions.Profit;
                              pos.Cost += positions.Price * positions.Amount;
                            }
                        }
                  }
                  // record pre position
                  if (!obj.task.init) {
                        obj.task.init = true;
                        if (pos) {
                            obj.task.preAmount = pos.Amount;
                            obj.task.preCost = pos.Cost;
                        } else {
                            obj.task.preAmount = 0;
                            obj.task.preCost = 0;
                        }
                  }
                  var remain = obj.task.amount;
                  if (pos) {
                        obj.task.dealAmount = pos.Amount - obj.task.preAmount;
                        remain = parseInt(obj.task.amount - obj.task.dealAmount);
                        if (remain <= 0 || obj.task.retry >= MaxTaskRetry) {
                            ret = {
                              price: (pos.Cost - obj.task.preCost) / (pos.Amount - obj.task.preAmount),
                              amount: (pos.Amount - obj.task.preAmount),
                              position: pos
                            };
                            break;
                        }
                  } else if (obj.task.retry >= MaxTaskRetry) {
                        ret = null;
                        break;
                  }

                  var depth = exchange.GetDepth();
                  if (!depth) {
                        return ERR_GET_DEPTH;
                  }
                  var orderId = null;
                  if (obj.task.action == ACT_LONG) {
                        exchange.SetDirection("buy");
                        orderId = exchange.Buy(_N(depth.Asks.Price + (insDetail.PriceTick * SlideTick), 2), Math.min(remain, depth.Asks.Amount), obj.symbol, 'Ask', depth.Asks);
                  } else {
                        exchange.SetDirection("sell");
                        orderId = exchange.Sell(_N(depth.Bids.Price - (insDetail.PriceTick * SlideTick), 2), Math.min(remain, depth.Bids.Amount), obj.symbol, 'Bid', depth.Bids);
                  }
                  // symbol not in trading or other else happend
                  if (!orderId) {
                        obj.task.retry++;
                        return ERR_TRADE;
                  }
                } while (true);
            }
            if (obj.task.onFinish) {
                obj.task.onFinish(ret);
            }
            obj.setTask(ACT_IDLE);                                                               // 任务执行完成(中间没有被 错误 return),重设为 空闲任务
            return ERR_SUCCESS;
      };
      obj.Poll = function(subroutine) {                                                         // 处理海龟交易法策略逻辑, 参数:子程序?
            obj.status.isTrading = $.IsTrading(obj.symbol);                                       // 调用 模板的导出函数 $.IsTrading 检测 obj.symbol 记录的品种是否在交易时间,结果赋值给obj.status.isTrading
            if (!obj.status.isTrading) {                                                            // 如果 obj.status.isTrading 是 false 即 不在交易时间内, return 返回
                return;
            }
            if (obj.task.action != ACT_IDLE) {                                                      // 如果 任务属性的 执行动作属性 不等于 等待标记(宏)
                var retCode = obj.processTask();                                                    // 就调用 当前obj 对象的processTask函数 执行 task 记录的任务。
                if (obj.task.action != ACT_IDLE) {                                                // 如果 调用 processTask 函数后 task属性的action 属性不等于 等待标记,即证明任务没有处理成功。
                  obj.setLastError("任务没有处理成功: " + errMsg + ", " + obj.task.desc + ", 重试: " + obj.task.retry);
                  // 此时调用 setLastError 记录 并 显示 任务 没有处理成功, 错误代码, 任务描述、重试次数
                } else {
                  obj.setLastError();                                                             // 调用 setLastError 不传参数, 不传参数 用空内容(字符串,详见函数setLastError)刷新。
                }
                return;                                                                           // 执行完 任务 返回
            }
            if (typeof(subroutine) !== 'undefined' && subroutine) {                                 // 参数 subroutine 不为null 且 已定义, 比如在调用 setTask 后会执行Poll,到此就返回
                return;                                                                           // 返回
            }
            // Loop
            var suffix = WXPush ? '@' : '';                                                         // 界面参数如果开启 微信推送, suffix 会被赋值 "@"(微信推送功能 只用在API: Log函数后加 "@"字符即可), 否则空字符。
            // switch symbol
            _C(exchange.SetContractType, obj.symbol);                                             // 切换 合约 为 obj.symbol 记录的合约代码
            var records = exchange.GetRecords();                                                    // 获取K线数据
            if (!records) {                                                                         // 如果 K线获取到null 值
                obj.setLastError("获取K线失败");                                                      // 设置失败信息,并返回。
                return;
            }
            obj.status.recordsLen = records.length;                                                 // 记录K线长度
            if (records.length < obj.atrLen) {                                                      // 如果 K线长度小于ATR指标参数(小于的话 无法计算出ATR指标 即N值)
                obj.setLastError("K线长度小于 " + obj.atrLen);                                        // 设置错误信息,并返回。
                return;
            }
            var opCode = 0; // 0: IDLE, 1: LONG, 2: SHORT, 3: CoverALL                              // 声明一个临时变量操作代码 有4种操作
            var lastPrice = records.Close;                                    // 声明一个临时变量用K线 最后一个柱 的收盘价给其赋值,(K线最后一个柱的收盘价是实时更新的是最新价格)
            obj.lastPrice = lastPrice;                                                            // 赋值给obj.lastPrice
            if (obj.marketPosition === 0) {                                                         // 如果当前 海龟策略 控制对象的加仓次数 为0 ,即没持仓。
                obj.status.stopPrice = '--';                                                      // 给止损价 赋值 '--'
                obj.status.leavePrice = '--';                                                       // 用于显示 状态的表格 对象 status的 leavePrice属性赋值 "--"(因为没有持仓,所以没有 离市价)
                obj.status.upLine = 0;                                                            // 赋值 上线,(这里如果不明白 这些变量控制那些显示,可以实际运行一个模拟盘 ,看下界面对比分析更好理解。)
                obj.status.downLine = 0;                                                            // 赋值 下线
                for (var i = 0; i < 2; i++) {                                                       // 在当前的分支条件内,是没有持仓的,这里循环两次,用来检测2个突破系统的触发。
                  if (i == 0 && obj.useFilter && !obj.preBreakoutFailure) {                     // 如果是第一次循环,并且启用了入市条件过滤,并且上次突破没有失败。
                        continue;                                                                   // 跳过本次循环
                  }
                  var enterPeriod = i == 0 ? obj.enterPeriodA : obj.enterPeriodB;               // 用 ? :三元条件表达式,选择使用的突破系统 参数,即当 i == 0 时 使用 系统A
                  if (records.length < (enterPeriod + 1)) {                                       // 限制 当前 K线周期 bar 长度 必须大于 突破系统的入市周期加1
                        continue;                                                                   // 跳过本次循环
                  }
                  var highest = TA.Highest(records, enterPeriod, 'High');                         // 计算enterPeriod周期内所有最高价的 最大值
                  var lowest = TA.Lowest(records, enterPeriod, 'Low');                            // 计算enterPeriod周期内所有最低价的 最小值
                  obj.status.upLine = obj.status.upLine == 0 ? highest : Math.min(obj.status.upLine, highest);      // 取两次 系统A 和系统B 获取的 highest中 最小的值
                  obj.status.downLine = obj.status.downLine == 0 ? lowest : Math.max(obj.status.downLine, lowest);// 取两次 系统A 和系统B 获取的 lowest中 最大的值
                  if (lastPrice > highest) {                                                                        // 最新的 价格 如果向上突破 对应周期内的最高价
                        opCode = 1;                                                                                 // 操作值 赋值1
                  } else if (lastPrice < lowest) {                                                                  // 最新的 价格 如果向下突破 对应周期内的最低价
                        opCode = 2;                                                                                 // 操作值 赋值2
                  }
                  obj.leavePeriod = (enterPeriod == obj.enterPeriodA) ? obj.leavePeriodA : obj.leavePeriodB;      // 更新 最终确定使用 哪个系统?问题1
                }
            } else {                                                                                                // 如果持有仓位
                var spread = obj.marketPosition > 0 ? (obj.openPrice - lastPrice) : (lastPrice - obj.openPrice);      // 计算单价盈亏 做多 盈利是负值 亏损是正值,因为要做和止损单价的对比,所以取反, 做空同理
                obj.status.stopPrice = _N(obj.openPrice + (obj.N * StopLossRatio * (obj.marketPosition > 0 ? -1 : 1)));// 计算止损价 做多的时候: 用开仓价 减去 N值 乘 止损系数, 做空: 用开仓价 加上 N值 乘止 损系数。
                if (spread > (obj.N * StopLossRatio)) {                                                                  // 检测 单价盈亏 是否大于 设定的 盈亏限制(即 止损系数 * N值)
                  opCode = 3;                                                                                          // 触发 止损 操作代码 赋值 3
                  obj.preBreakoutFailure = true;                                                                     // 触发止损,标记 上次突破失败为真
                  Log(obj.symbolDetail.InstrumentName, "止损平仓", suffix);                                             // 打印 该品种 合约名 止损, 如果开启微信推送,则推送到微信。
                  obj.status.st++;                                                                                     // 止损计数 累计
                } else if (-spread > (IncSpace * obj.N)) {                                                               // 如果单价盈亏(取反 得 正 盈利数,负亏损数) 大于加仓系数 * N值, 触发加仓操作
                  opCode = obj.marketPosition > 0 ? 1 : 2;                                                             // 0: IDLE, 1: LONG, 2: SHORT, 3: CoverALL
                } else if (records.length > obj.leavePeriod) {                                                         // 只要 K线周期 长度大于 离市 周期,可以计算离市价格
                  obj.status.leavePrice = TA.Lowest(records, obj.leavePeriod, obj.marketPosition > 0 ? 'Low' : 'High') // 问题2
                  if ((obj.marketPosition > 0 && lastPrice < obj.status.leavePrice) ||                                 // 做多 或者 做空 如果触发了离市价
                        (obj.marketPosition < 0 && lastPrice > obj.status.leavePrice)) {
                        obj.preBreakoutFailure = false;                                                                  // 上次突破失败 赋值为 false ,即 没失败
                        Log(obj.symbolDetail.InstrumentName, "正常平仓", suffix);                                       //打印信息 平仓,可微信推送
                        opCode = 3;                                                                                    // 给操作 赋值 3
                        obj.status.cover++;                                                                               // 平仓计数累计
                  }
                }
            }

            if (opCode == 0) {                                                                                          // 如果是等待 代码则返回
                return;
            }
            if (opCode == 3) {                                                                                          // 如果是 全平仓代码
                obj.setTask(ACT_COVER, 0, function(ret) {                                                               // 调用 obj 海龟控制对象的成员函数 setTask 设置任务 (全平仓)并自定义一个回调函数(第三个参数 function(ret){...} 就是匿名函数。)
                  obj.reset();                                                                                          // 回调函数 会在setTask 函数中 设置任务后 调用的 Poll 的函数中 通过 processTask 函数 执行该任务完成后 ,触发回调函数。
                  _G(obj.symbol, null);                                                                                 // 回调函数 调用了 不传参数的 reset函数,执行控制对象 变量重置工作,清空 _G 保存的 本地永久 数据(用于恢复,因为已经平仓了,所以需要清空)
                });
                return;                                                                                                   // 回调函数是在任务完成后(即 全部海龟头寸 平仓后 才触发,此处只是预设)
            }
            // Open
            if (Math.abs(obj.marketPosition) >= obj.maxLots) {                                                            // 建仓 或者 加仓处理, 这里判断如果 加仓次数 大于等于 最大允许加仓次数
                obj.setLastError("禁止开仓, 超过最大持仓 " + obj.maxLots);                                                    // 设置错误信息,然后返回。
                return;
            }
            var atrs = TA.ATR(records, atrLen);                                                                            // 计算ATR 指标
            var N = _N(atrs, 4);                                                                        // 获取 当前ATR指标值 ,即 N值

            var account = _bot.GetAccount();                                                                               // 调用 模板 生成的 交易控制对象的 成员函数 GetAccount
            var currMargin = JSON.parse(exchange.GetRawJSON()).CurrMargin;                                                 // 获取当前 保证金数值
            var unit = parseInt((account.Balance+currMargin-obj.keepBalance) * (obj.riskRatio / 100) / N / obj.symbolDetail.VolumeMultiple);
            // 计算 总 可用资金对应 N值 计算出的一个头寸的 大小(手数)。可以看原版的海龟交易法关于 unit 的计算,知乎上也有相关文章。
            var canOpen = parseInt((account.Balance-obj.keepBalance) / (opCode == 1 ? obj.symbolDetail.LongMarginRatio : obj.symbolDetail.ShortMarginRatio) / (lastPrice * 1.2) / obj.symbolDetail.VolumeMultiple);
            // 根据 要做 多仓 或者 空仓 的保证金率 计算 可用资金 可以开 的手数,可开量。
            unit = Math.min(unit, canOpen);                                                                              // 最终头寸大小 取 unit, canOpen 中最小值
            if (unit < obj.symbolDetail.MinLimitOrderVolume) {                                                             // 如果 计算出的 头寸大小小于 合约规定的限价单 最小下单量,则
                obj.setLastError("可开 " + unit + " 手 无法开仓, " + (canOpen >= obj.symbolDetail.MinLimitOrderVolume ? "风控触发" : "资金限制"));// 设置最新错误信息
                return;                                                                                                                        // 返回
            }
            obj.setTask((opCode == 1 ? ACT_LONG : ACT_SHORT), unit, function(ret) {                                        // 根据opCode 设定,调用 setTask 函数 设定任务
                if (!ret) {                                                                                                // 同样 第三个参数 是回调函数,回调函数中 ret 是触发 调用回调函数时传入的参数,任务的执行返回值。
                  obj.setLastError("下单失败");
                  return;
                }
                Log(obj.symbolDetail.InstrumentName, obj.marketPosition == 0 ? "开仓" : "加仓", "离市周期", obj.leavePeriod, suffix);// 任务成功完成,回调函数会执行此 输出
                obj.N = N;                                                                                                          // 开仓 或者 加仓后 更新N值
                obj.openPrice = ret.price;                                                                                          // 更新 开仓价格
                obj.holdPrice = ret.position.Price;                                                                                 // 更新持仓均价,根据 任务执行的ret。
                if (obj.marketPosition == 0) {                                                                                    // 如果此时 加仓次数是0, 即代表本次是 建仓
                  obj.status.open++;                                                                                              // 开仓计数 累计
                }
                obj.holdAmount = ret.position.Amount;                                                                               // 更新持仓量
                obj.marketPosition += opCode == 1 ? 1 : -1;                                                                         // 根据 做多 或者 做空 累计 加仓次数
                obj.status.vm = ;                  // 更新 用于恢复的 字符串 ,属性vm
                _G(obj.symbol, obj.status.vm);                                                                                    // 本地持久化储存 当前持仓信息。
            });
      };                                                                                                                        // Poll 函数结束
      var vm = null;                                                                                                            // 在New 构造函数中 声明一个 局部变量 vm 区别于obj.vm
      if (RMode === 0) {                                                                                                          // 如果进度恢复模式为 自动,下拉框第一个索引是0 ,设置为第一个时 下拉框参数就返回0 ,第二个 返回下一个索引1,以此类推。
            vm = _G(obj.symbol);                                                                                                    // 取回 持久化储存的数据 赋值给 局部变量vm
      } else {                                                                                                                  // 否则 恢复模式为 手动
            vm = JSON.parse(VMStatus);                                                                                  // 取手动恢复字符串 JSON解析后的数组中的对应于合约类型 obj.symbol 的 数据。
      }
      if (vm) {                                                                                                                   // 如果获取的有 数据
            Log("准备恢复进度, 当前合约状态为", vm);                                                                                     // 输出恢复的 合约状态
            obj.reset(vm, vm, vm, vm, vm);                                                                            // 调用重设 函数 重新设置 恢复状态
      } else {                                                                                                                     // 如果vm 没有数据
            if (needRestore) {                                                                                                       // 需要恢复 则输出 没找到进度的信息, (有可能是 合约列表 中 有新的合约代码,则不需要恢复)
                Log("没有找到" + obj.symbol + "的进度恢复信息");
            }
            obj.reset();                                                                                                             // reset 不传参数 ,即重置
      }
      return obj;                                                                                                                  // 返回 构造完成的对象。
    }
};

summerXXX 发表于 2017-3-22 13:06


function onexit() {                                                                // 策略程序 退出时执行。
    Log("已退出策略...");
}

function main() {
    if (exchange.GetName().indexOf('CTP') == -1) {                                 // 限定 连接的交易所 必须是 CTP 商品期货
      throw "只支持商品期货CTP";
    }
    SetErrorFilter("login|ready|流控|连接失败|初始|Timeout");                         // 过滤常规错误
    var mode = exchange.IO("mode", 0);                                             // 设定行情模式 为立即返回模式 参看 API 文档
    if (typeof(mode) !== 'number') {                                             // 如果 切换模式 的API 返回的 不是 数值,即切换失败。
      throw "切换模式失败, 请更新到最新托管者!";                                    // 抛出异常
    }
    while (!exchange.IO("status")) {                                             // 检测 与 行情、交易服务器连接,直到API函数exchange.IO("status") 返回true 连接上,退出循环
      Sleep(3000);
      LogStatus("正在等待与交易服务器连接, " + new Date());                        // 在未连接上时输出 文本和 当前时间。
    }
    var positions = _C(exchange.GetPosition);                                    // 调用APIGetPosition 函数 获取 持仓信息
    if (positions.length > 0) {                                                    // 返回的数组不是空数组 ,即有持仓
      Log("检测到当前持有仓位, 系统将开始尝试恢复进度...");
      Log("持仓信息", positions);
    }
    Log("风险系数:", RiskRatio, "N值周期:", ATRLength, "系统1: 入市周期", EnterPeriodA, "离市周期", LeavePeriodA, "系统二: 入市周期", EnterPeriodB, "离市周期", LeavePeriodB, "加仓系数:", IncSpace, "止损系数:", StopLossRatio, "单品种最多开仓:", MaxLots, "次");
    // 输出 参数信息。
    var initAccount = _bot.GetAccount();                                           // 获取账户信息
    var initMargin = JSON.parse(exchange.GetRawJSON()).CurrMargin;               // 调用 API GetRawJSON 函数 获取: "CurrMargin": "当前保证金总额",
    var keepBalance = _N((initAccount.Balance + initMargin) * (KeepRatio/100), 3); // 根据预留保证金比例 计算出 需要预留的资金。
    Log("资产信息", initAccount, "保留资金:", keepBalance);                           // 输出信息
   
    var tts = [];
    var filter = [];                                                               // 过滤用数组
    var arr = Instruments.split(',');                                              // 合约列表按照逗号分隔 成数组
    for (var i = 0; i < arr.length; i++) {                                       // 遍历分隔后的数组
      var symbol = arr.replace(/^\s+/g, "").replace(/\s+$/g, "");             // 正则表达式 匹配 操作, 得出合约代码
      if (typeof(filter) !== 'undefined') {                              // 如果 在过滤数组中 存在 名为 symbol的属性,则显示信息 并跳过。
            Log(symbol, "已经存在, 系统已自动过滤");
            continue;
      }
      filter = true;                                                   // 给过滤数组 添加 名为 symbol 的 属性,下次 同样的 合约代码 会被过滤
      var hasPosition = false;                                                   // 初始化 hasPosition 变量 false 代表没有持仓
      for (var j = 0; j < positions.length; j++) {                               // 遍历 获取到的持仓信息
            if (positions.ContractType == symbol) {                           // 如果有持仓信息 合约 名称 和 symbol一样的, 给hasPosition 赋值true 代表有持仓
                hasPosition = true;
                break;
            }
      }
      var obj = TTManager.New(hasPosition, symbol, keepBalance, RiskRatio, ATRLength, EnterPeriodA, LeavePeriodA, EnterPeriodB, LeavePeriodB, UseEnterFilter, IncSpace, StopLossRatio, MaxLots);
      // 根据界面参数 使用 构造函数 New 构造一个品种的海龟交易策略控制对象
      tts.push(obj);                                                             // 把该对象压入 tts 数组, 最终根据合约列表 ,生成了若干个品种的 控制对象储存在tts数组
    }
   

    var preTotalHold = -1;
    var lastStatus = '';
    while (true) {                                                               // 主要循环
      if (GetCommand() === "暂停/继续") {                                       // API GetCommand 函数 获取 程序界面上的 命令。此处 如果 点击了界面上的“暂停/继续”按钮
            Log("暂停交易中...");
            while (GetCommand() !== "暂停/继续") {                                  // 进入等待循环 ,直到再次点击“暂停/继续” 按钮 退出 等待循环
                Sleep(1000);
            }
            Log("继续交易中...");
      }
      while (!exchange.IO("status")) {                                           // 一旦断开服务器的连接,则尝试重连 并等待。
            Sleep(3000);
            LogStatus("正在等待与交易服务器连接, " + new Date() + "\n" + lastStatus);// 输出上一次的 状态栏 内容,并 更新时间。
      }
      var tblStatus = {                                                          // 用于显示在状态栏表格上的持仓信息对象
            type: "table",
            title: "持仓信息",
            cols: ["合约名称", "持仓方向", "持仓均价", "持仓数量", "持仓盈亏", "加仓次数", "开仓次数", "止损次数", "成功次数", "当前价格", "N"],
            rows: []
      };
      var tblMarket = {                                                          // 用于显示在状态栏表格上的 市场信息 对象
            type: "table",
            title: "运行状态",
            cols: ["合约名称", "合约乘数", "保证金率", "交易时间", "柱线长度", "上线", "下线", "止损价", "离市价", "异常描述", "发生时间"],
            rows: []
      };
      var totalHold = 0;
      var vmStatus = {};
      var ts = new Date().getTime();                                             // 当前时间戳
      var holdSymbol = 0;                                                      // 持有的合约量
      for (var i = 0; i < tts.length; i++) {                                     // 遍历tts数组
            tts.Poll();                                                         // 调用每个 合约的海龟管理对象的 Poll 函数
            var d = tts.Status();                                             // 更新每个 海龟管理对象的状态 属性 status 并返回。
            if (d.holdAmount > 0) {                                                // 如果当前索引的对象 有 持仓
                vmStatus = d.vm;                                       // 给空对象 vmStatus 添加合约名称 为属性名 的属性,并给其赋值 持仓信息vm
                holdSymbol++;                                                      // 给持有的合约品种数量累计
            }
            tblStatus.rows.push();
            // 压入当前 索引 的 海龟管理对象 的信息 到状态分页表格
            tblMarket.rows.push();
            // 压入当前 索引 的 海龟管理对象 的信息 到行情分页表格
            totalHold += Math.abs(d.holdAmount);                                 // 值为回调函数 的参数ret 的属性 更新,可以参见 回调函数的 传入实参。processTask 函数中的 ret
            // 累计 总持仓手数
      }
      var now = new Date();                                                      // 获取最新时间
      var elapsed = now.getTime() - ts;                                          // 计算主要耗时代码 , 迭代 执行 Poll 函数的 开始与结束的 时间差。
      var tblAssets = _bot.GetAccount(true);                                     // 获取账户详细信息并返回一个表格对象。(因为参数传递的是true, 参见 模板的 GetAccount 函数的 getTable 参数)
      var nowAccount = _bot.Account();                                           // 获取账户信息
      
      if (tblAssets.rows.length > 10) {                                          // 如果获取的 表格的 行数 大于10
            // replace AccountId
            tblAssets.rows = ["InitAccount", "初始资产", initAccount];         // 设置 索引 0 的行数 为 初始资金信息。
      } else {
            tblAssets.rows.unshift(["NowAccount", "当前可用", nowAccount], ["InitAccount", "初始资产", initAccount]); // 往 rows 数组 中开始的位置插入2个元素
      }
      lastStatus = '`' + JSON.stringify() + '`\n轮询耗时: ' + elapsed + ' 毫秒, 当前时间: ' + now.toLocaleString() + ', 星期' + ['日', '一', '二', '三', '四', '五', '六'] + ", 持有品种个数: " + holdSymbol;
      // 组合 各种 用于显示在界面的信息。
      if (totalHold > 0) {                                                       // 在有持仓时才 显示 手动恢复字符串(vmStatus JSON序列化)
            lastStatus += "\n手动恢复字符串: " + JSON.stringify(vmStatus);
      }
      LogStatus(lastStatus);                                                   // 调用API 显示在 状态栏
      if (preTotalHold > 0 && totalHold == 0) {                                  // 当全部持仓 平掉 没有持仓时
            LogProfit(nowAccount.Balance - initAccount.Balance - initMargin);      // 输出 盈利, 显示到收益曲线(此种情况 出现概率较低,很难有同时全部都未持仓的状态,所以收益都是 动态的,可以看 账户详细信息分析当前状况)
      }
      preTotalHold = totalHold;                                                // 每次都更新确保 输出收益只显示一次。
      Sleep(LoopInterval * 1000);                                                // 轮询等待。避免API 访问过于频繁
    }
}
```

菜鸟程序员注释,如有错误,欢迎斧正,感谢支持! ^。^
页: [1]
查看完整版本: 多品种海龟 期货 源码 翻译版