Mạng lưới có thể tự định hướng Những người mua sau khi bán: Mạng lưới sẽ bắt đầu từ mức giá đầu tiên và treo thanh toán xuống, mỗi thanh toán khoảng cách giữa giá và giá. Các tham số này, số lượng thanh toán treo là "số đơn", treo đủ. Tổng số thanh toán treo. Một thanh toán, sau khi giao dịch thanh toán tùy ý, chương trình sẽ thêm giá trên cơ sở giá mua. Những người bán trước mua sau: Hoạt động ngược lại.
Rủi ro lớn nhất của chiến lược là thị trường đơn phương, với sự biến động của giá vượt quá phạm vi lưới.
Mạng lưới có khả năng dừng tự động và di chuyển
Đúng là một giấc mơ. - Không.https://www.fmz.com/bbs-topic/334Tóm lại - Các nhà phát minh định lượng - Không.https://www.fmz.com/bbs-topic/1069Mạng lưới một bên của chiến lược biến dạng lưới (phiên bản chú thích)
/*backtest start: 2021-08-27 00:00:00 end: 2021-08-28 00:00:00 period: 1d basePeriod: 1h exchanges: [{"eid":"Huobi","currency":"BTC_USDT"}] args: [["OpType",1]] */ // 小小梦 梦回 // https://www.fmz.com/bbs-topic/334 汇总之 - 发明者量化 视频教学与图文教学 //https://www.fmz.com/bbs-topic/1069 网格变形策略之单边网格 (注释版) /* 界面参数 (代码中体现为全局变量) 变量 描述 类型 默认值 OpType 网格方向 下拉框(selected) 先买后卖|先卖后买 FirstPriceAuto 首价格自动 布尔型(true/false) true FirstPrice@!FirstPriceAuto 首价格 数字型(number) 100 AllNum 总数量 数字型(number) 10 PriceGrid 价格间隔 数字型(number) 1 PriceDiff 价差(元) 数字型(number) 2 AmountType 订单大小 下拉框(selected) 买卖同量|自定义量 AmountOnce@AmountType==0 单笔数量 数字型(number) 0.1 BAmountOnce@AmountType==1 买单大小 数字型(number) 0.1 SAmountOnce@AmountType==1 卖单大小 数字型(number) 0.1 AmountCoefficient@AmountType==0 量差 字符串(string) *1 AmountDot 量小数点最长位数 数字型(number) 3 EnableProtectDiff 开启价差保护 布尔型(true/false) false ProtectDiff@EnableProtectDiff 入市价差保护 数字型(number) 20 CancelAllWS 停止时取消所有挂单 布尔型(true/false) true CheckInterval 轮询间隔 数字型(number) 2000 Interval 失败重试间隔 数字型(number) 1300 RestoreProfit 恢复上次盈利 布尔型(true/false) false LastProfit@RestoreProfit 上次盈利 数字型(number) 0 ProfitAsOrg@RestoreProfit 上次盈利算入均价 布尔型(true/false) false EnableAccountCheck 启用资金检验 布尔型(true/false) true EnableStopLoss@EnableAccountCheck 开启止损 布尔型(true/false) false StopLoss@EnableStopLoss 最大浮动亏损(元) 数字型(number) 100 StopLossMode@EnableStopLoss 止损后操作 下拉框(selected) 回收并退出|回收再撒网 EnableStopWin@EnableAccountCheck 开启止盈 布尔型(true/false) false StopWin@EnableStopWin 最大浮动盈利(元) 数字型(number) 120 StopWinMode@EnableStopWin 止盈后操作 下拉框(selected) 回收并退出|回收再撒网 AutoMove@EnableAccountCheck 自动移动 布尔型(true/false) false MaxDistance@AutoMove 最大距离(元) 数字型(number) 20 MaxIdle@AutoMove 最大空闲(秒) 数字型(number) 7200 EnableDynamic 开启动态挂单 布尔型(true/false) false DynamicMax@EnableDynamic 订单失效距离(元) 数字型(number) 30 ResetData 启动时清空所有数据 布尔型(true/false) true Precision 价格小数位长度 数字型(number) 5 */ function hasOrder(orders, orderId) // { // 检测 参数 orders 中 是否有 ID 为 orderId 的订单 for (var i = 0; i < orders.length; i++) // { // 遍历 orders 检测 是否 有相同的 id , 找到 返回 true if (orders[i].Id == orderId) // { return true; } } return false; // 全部遍历完 ,没有触发 if 则 没有找到 ID 为 orderId 的订单, 返回 false } function cancelPending() // { // 取消所有挂单 函数 var ret = false; // 设置 返回成功 标记变量 while (true) // { // while 循环 if (ret) // { // 如果 ret 为 true 则 Sleep 一定时间 Sleep(Interval); } var orders = _C(exchange.GetOrders); // 调用 API 获取 交易所 未完成的订单信息 if (orders.length == 0) // { // 如果返回的是 空数组, 即 交易所 没有未完成的订单。 break; // 跳出 while 循环 } for (var j = 0; j < orders.length; j++) // { // 遍历 未完成的 订单数组, 并根据索引j 逐个使用 orders[j].Id 去 取消订单。 exchange.CancelOrder(orders[j].Id, orders[j]); ret = true; // 一旦有取消操作, ret 赋值 为 true 。用于触发 以上 Sleep , 等待后重新 exchange.GetOrders 检测 } } return ret; // 返回 ret } function valuesToString(values, pos) // { // 值 转换 为字符串 var result = ''; // 声明一个用于返回的 空字符串 result if (typeof (pos) === 'undefined') // { // 如果 没有传入 pos 这个参数 ,给 pos 赋值 0 pos = 0; } for (var i = pos; i < values.length; i++) // { // 根据 传入的 pos 处理 values 数组 if (i > pos) // { // 除了第一次 循环 之后 在result 字符串后 加上 ' ' 一个空格 result += ' '; } if (values[i] === null) // { // 如果 values (函数 参数列表 数组) 当前的索引的 元素 为 null 则 result 添加 'null'字符串 result += 'null'; }// else if (typeof (values[i]) == 'undefined') // { // 如果 是 未定义的, 则添加 'undefined' result += 'undefined'; }// else // { // 剩余类型 做 switch 检测 分别处理 switch (values[i].constructor.name) // { // 检查 values[i] 的 constructor 的 name 属性, 即类型 名称 case 'Date': case 'Number': case 'String': case 'Function': result += values[i].toString(); // 如果是 日期类型、数值类型、字符串类型、函数类型 ,调用其 toString 函数 转换为字符串 后,添加 break; default: result += JSON.stringify(values[i]); // 其他情况 则 使用JSON.stringify 函数 转换为 JSON 字符串 添加到 result break; } } } return result; // 返回 result } function Trader() // { // Trader 函数 ,使用闭包。 var vId = 0; // 订单递增ID var orderBooks = []; // 订单薄 var hisBooks = []; // 历史订单薄 var orderBooksLen = 0; // 订单薄长度 this.Buy = function (price, amount, extra) // { // 买函数, 参数: 价格 、数量、扩展信息 if (typeof (extra) === 'undefined') // { // 如果参数 extra 未传入,即 typeof 返回 undefined extra = ''; // 给 extra 赋值空字符串 }// else // { extra = valuesToString(arguments, 2); // 调用 this.Buy 函数 时传入的参数 arguments ,传入 valuesToString函数中 } vId++; // var orderId = "V" + vId; // orderBooks[orderId] = // { // 向订单薄 数组中添加 属性 orderId, 用构造的 对象 对其初始化。 Type: ORDER_TYPE_BUY, // 构造的对象 Type 属性: 类型 买单 Status: ORDER_STATE_PENDING, // 状态 挂起 Id: 0, // 订单ID 0 Price: price, // 价格 参数 price Amount: amount, // 订单量 参数 amount Extra: extra // 扩展信息 经valuesToString 处理过的字符串。 }; orderBooksLen++; // 订单薄的长度 累计加1 return orderId; // 返回 本次构造的订单的 orderId (非交易所订单ID ,别混淆。) }; this.Sell = function (price, amount, extra) // { // 和 thie.Buy 基本类似, 构造卖单。 if (typeof (extra) === 'undefined') // { extra = ''; }// else // { extra = valuesToString(arguments, 2); } vId++; var orderId = "V" + vId; orderBooks[orderId] = // { Type: ORDER_TYPE_SELL, Status: ORDER_STATE_PENDING, Id: 0, Price: price, Amount: amount, Extra: extra }; orderBooksLen++; return orderId; }; this.GetOrders = function () // { // 获取未完成的订单信息 var orders = _C(exchange.GetOrders); // 调用 API GetOrders 获取 未完成的订单信息 赋值给 orders for (orderId in orderBooks) // { // 遍历 Trader 对象中的 orderBooks var order = orderBooks[orderId]; // 根据 orderId 取出 订单 if (order.Status !== ORDER_STATE_PENDING) // { // 如果 order 的状态不等于 挂起状态 ,就跳过本次循环 continue; } var found = false; // 初始化 found 变量(标记 是否找到) 为 true for (var i = 0; i < orders.length; i++) // { // 遍历 API 返回的未完成的订单的数据 if (orders[i].Id == order.Id) // { // 找到 和 orderBooks 中 未完成订单 , id 相同的订单时,给found 赋值 true,代表找到。 found = true; break; // 跳出当前循环 } } if (!found) // { // 如果 没有找到,则 向 orders push orderBooks[orderId]。 orders.push(orderBooks[orderId]); // 为何要这样 push ? } } return orders; // 返回 orders } this.GetOrder = function (orderId) // { // 获取订单 if (typeof (orderId) === 'number') // { // 如果传入的 参数 orderId 是数值类型 return exchange.GetOrder(orderId); // 调用 API GetOrder 根据 orderId 获取 订单信息并返回。 } if (typeof (hisBooks[orderId]) !== 'undefined') // { // typeof(hisBooks[orderId]) 如果不等于 未定义的 return hisBooks[orderId]; // 返回 hisBooks 中 属性为 orderId 的数据 } if (typeof (orderBooks[orderId]) !== 'undefined') // { // 同上, orderBooks 中如果有 属性为 orderId的值存在, 返回这个数据。 return orderBooks[orderId]; } return null; // 如果不符合上述条件触发, 返回 null }; this.Len = function () // { // 返回 Trader 的 orderBookLen 变量, 即返回订单薄长度。 return orderBooksLen; }; this.RealLen = function () // { // 返回 订单薄中 激活订单数量。 var n = 0; // 初始计数 为 0 for (orderId in orderBooks) // { // 遍历 订单薄 if (orderBooks[orderId].Id > 0) // { // 如果 在遍历中 当前 的订单的 Id 大于0 ,即 非初始时的0, 表明订单已下单,该订单已经激活。 n++; // 累计 已经激活的订单 } } return n; // 返回 n值, 即返回 真实 订单薄长度。(已激活订单数量) }; this.Poll = function (ticker, priceDiff) // { // var orders = _C(exchange.GetOrders); // 获取 所有未完成的订单 for (orderId in orderBooks) // { // 遍历 订单薄 var order = orderBooks[orderId]; // 取出当前 的订单 赋值给 order if (order.Id > 0) // { // 如果订单 为 激活状态,即 order.Id 不为0(已经下过单) var found = false; // 变量 found(标记找到) 为 false for (var i = 0; i < orders.length; i++) // { // 在交易所返回的 未完成订单信息中 查找 相同的订单号 if (order.Id == orders[i].Id) // { // 如果查找到, 给found 赋值 true ,代表已找到。 found = true; } } if (!found) // { // 如果当前的 orderId 代表的订单 没有在 交易所返回的未完成订单数组orders中找到对应的。 order.Status = ORDER_STATE_CLOSED; // 给 orderBooks 中对应 orderId 的订单(即当前的order变量)更新,Status 属性更新为 ORDER_STATE_CLOSED (即 已关闭) hisBooks[orderId] = order; // 完成的订单 记录在 历史订单薄里,即 hisBooks ,统一,且唯一的订单号 orderId delete (orderBooks[orderId]); // 删除 订单薄的 名为 orderId值的 属性。(完成的订单 从中 删除) orderBooksLen--; // 订单薄 长度自减 continue; // 以下代码 跳过继续循环。 } } var diff = _N(order.Type == ORDER_TYPE_BUY ? (ticker.Buy - order.Price) : (order.Price - ticker.Sell)); // diff 为 当前订单薄 中 订单的 计划开仓价和 当前实时开仓价格的差值。 var pfn = order.Type == ORDER_TYPE_BUY ? exchange.Buy : exchange.Sell; // 根据订单的类型,给 pfn 赋值相应的 API 函数 引用。 // 即 如果 order 的类型是买单 , pfn 就是 exchange.Buy 函数的引用, 卖单同理。 if (order.Id == 0 && diff <= priceDiff) // { // 如果 订单薄中的订单 order 没有激活(即Id 等于0 ) 并且 当前价格距离 订单计划价格 小于等于 参数传入的 priceDiff var realId = pfn(order.Price, order.Amount, order.Extra + "(距离: " + diff + (order.Type == ORDER_TYPE_BUY ? (" 买一: " + ticker.Buy) : (" 卖一: " + ticker.Sell)) + ")"); // 执行下单函数 ,参数传入 价格、数量、 订单扩展信息 + 挂单距离 + 行情数据(买一 或者 卖一),返回 交易所 订单id if (typeof (realId) === 'number') // { // 如果 返回的 realId 是数值类型 order.Id = realId; // 赋值给 订单薄 当前的订单 order的 Id 属性。 } }// else if (order.Id > 0 && diff > (priceDiff + 1)) // { // 如果订单 处于激活状态, 并且 当前距离 大于 参数传入的 距离 var ok = true; // 声明一个 用于标记的变量 初始 true do // { // 先执行 do 再判断 while ok = true; // ok 赋值 true exchange.CancelOrder(order.Id, "不必要的" + (order.Type == ORDER_TYPE_BUY ? "买单" : "卖单"), "委托价:", order.Price, "量:", order.Amount, ", 距离:", diff, order.Type == ORDER_TYPE_BUY ? ("买一: " + ticker.Buy) : ("卖一: " + ticker.Sell)); // 取消 当前 超出 范围的挂单, 在取消订单这条日志后 打印 当前订单的信息、当前 距离 diff。 Sleep(200); // 等待 200 毫秒 orders = _C(exchange.GetOrders); // 调用 API 获取 交易所 中 未完成的订单。 for (var i = 0; i < orders.length; i++) // { // 遍历 这些未完成的订单。 if (orders[i].Id == order.Id) // { // 如果找到 取消的订单 还在 交易所未完成的订单数组中 ok = false; // 给 ok 这个变量赋值 false , 即没有 取消成功 } } } while (!ok); // 如果 ok 为 false,则 !ok 为 true ,while 就会继续重复循环,继续取消这个订单,并检测是否取消成功 order.Id = 0; // 给 order.Id 赋值 0 , 代表 当前这个订单 是未激活的。 } } }; } function balanceAccount(orgAccount, initAccount) // { // 平衡账户 函数 参数 策略启动时最初始的账户信息 , 本次撒网前初始账户信息 cancelPending(); // 调用自定义函数 cancelPending() 取消所有挂单。 var nowAccount = _C(exchange.GetAccount); // 声明一个 变量 nowAccount 用来记录 此刻 账户的最新信息。 var slidePrice = 0.2; // 设置下单时 的滑价 为 0.2 var ok = true; // 标记变量 初始 true while (true) // { // while 循环 var diff = _N(nowAccount.Stocks - initAccount.Stocks); // 计算出 当前 账户 和 初始账户 的币差 diff if (Math.abs(diff) < exchange.GetMinStock()) // { // 如果 币差的绝对值 小于 交易所 的最小交易量,break 跳出循环,不进行平衡操作。 break; } var depth = _C(exchange.GetDepth); // 获取 交易所深度信息 赋值给 声明的 depth 变量 var books = diff > 0 ? depth.Bids : depth.Asks; // 根据 币差 的大于0 或者 小于 0 ,提取 depth 中的 买单数组 或者 卖单数组(等于0 不会处理,在判断小于GetMinStock 的时候已经break) // 币差大于0 要卖出平衡,所以看买单数组, 币差小于0 相反。 var n = 0; // 声明 n 初始为 0 var price = 0; // 声明 price 初始 0 for (var i = 0; i < books.length; i++) // { // 遍历 买单 或者 卖单 数组 n += books[i].Amount; // 根据 遍历的索引 i , 累计每次的 订单的Amount (订单量) if (n >= Math.abs(diff)) // { // 如果 累计的 订单量 n 大于等于 币差,则: price = books[i].Price; // 获取 当前索引的订单的 价格,赋值给 price break; // 跳出 当前 for 遍历循环 } } var pfn = diff > 0 ? exchange.Sell : exchange.Buy; // 根据 币差 大于0 或者 小于 0 , 将 下卖单 API(exchange.Sell) 或者 下买单 API(exchange.Buy) 引用传递给 声明的 pfn var amount = Math.abs(diff); // 将要平衡操作的 下单量 为 diff 即 币差, 赋值给 声明的 amount 变量 var price = diff > 0 ? (price - slidePrice) : (price + slidePrice); // 根据币差 决定的 买卖方向 ,在 price 的基础上 增加 或者 减去 滑价(滑价是为了更容易成交),再赋值给 price Log("开始平衡", (diff > 0 ? "卖出" : "买入"), amount, "个币"); // 输出 日志 平衡的 币数。 if (diff > 0) // { // 根据币差 决定的 买卖方向 , 检测账户币数 或者 钱数是否足够。 amount = Math.min(nowAccount.Stocks, amount); // 确保下单量 amount 不会超过 当前 账户 的可用币数。 }// else // { amount = Math.min(nowAccount.Balance / price, amount); // 确保下单量 amount 不会超过 当前 账户 的可用钱数。 } if (amount < exchange.GetMinStock()) // { // 检测 最终下单数量 是否 小于 交易所 允许的最小下单量 Log("资金不足, 无法平衡到初始状态"); // 如果 下单量过小,则打印 信息。 ok = false; // 标记 平衡失败 break; // 跳出 while 循环 } pfn(price, amount); // 执行 下单 API (pfn 引用) Sleep(1000); // 暂停 1 秒 cancelPending(); // 取消所有挂单。 nowAccount = _C(exchange.GetAccount); // 获取当前 最新账户信息 } if (ok) // { // 当 ok 为 true (平衡成功) 时执行 花括号内代码 LogProfit(_N(nowAccount.Balance - orgAccount.Balance)); // 用传入的参数 orgAccount (平衡前的账户信息)的Balance 属性 减去当前的 账户信息的 Balance 属性,即 钱数之差, // 也就是 盈亏 (因币数不变,略有误差 因为有些很小的 量不能平衡) Log("平衡完成", nowAccount); // 输出日志 平衡完成。 } } var STATE_WAIT_OPEN = 0; // 用于 fishTable 中每个 节点的 状态 var STATE_WAIT_COVER = 1; // ... var STATE_WAIT_CLOSE = 2; // ... var ProfitCount = 0; // 盈亏次数 记录 var BuyFirst = true; // 初始 界面参数 var IsSupportGetOrder = true; // 交易所 是否支持 GetOrder API 函数, 全局变量, 用于 main 函数开始的判断 var LastBusy = 0; // 记录上次 处理的时间对象 function setBusy() // { // 设置 Busy 时间 LastBusy = new Date(); // 给 LastBusy 赋值当前的时间对象 } function isTimeout() // { // 判断是否超时 if (MaxIdle <= 0) // { // 最大空闲时间(基于 是否自动 移动网格), 如果 最大空闲时间 MaxIdle 设置小于等于0 return false; // 返回 false, 不判断 超时。即 总是返回false 未超时。 } var now = new Date(); // 获取当前时间对象 if (((now.getTime() - LastBusy.getTime()) / 1000) >= MaxIdle) // { // 使用当前时间对象的 getTime 函数 获取时间戳 与 LastBusy 的时间戳 计算差值, // 除以1000 算出 两个时间对象间 相差的秒数。 判断是否大于 最大空闲时间MaxIdle LastBusy = now; // 如果是大于, 更新 LastBusy 为当前时间对象 now return true; // 返回 true ,即超时。 } return false; // 返回 false 未超时 } function onexit() // { // 程序 退出 时的收尾函数。 if (CancelAllWS) // { // 设置了 停止时取消所有挂单,则 调用 cancelPending() 函数 取消所有挂单 Log("正在退出, 尝试取消所有挂单"); cancelPending(); } Log("策略成功停止"); Log(_C(exchange.GetAccount)); // 打印退出程序时的 账户持仓信息。 } function fishing(orgAccount, fishCount) // { // 撒网 参数 : 账户信息 ,撒网次数 setBusy(); // 设置 LastBuys 为当前 时间戳 var account = _C(exchange.GetAccount); // 声明一个 account 变量 , 获取当前 账户信息 并 赋值。 Log(account); // 输出 本次调用 fishing 函数 开始 时的账户信息。 var InitAccount = account; // 声明一个 变量 InitAccount 并用 account 赋值。 此处是 记录 本次 撒网 前的 初始账户资金,用于计算 浮动盈亏。 var ticker = _C(exchange.GetTicker); // 获取 行情 赋值给 声明的 ticker 变量 var amount = _N(AmountOnce); // 根据 界面参数 单笔数量,使用 _N 处理小数位(_N 默认 保留2位),赋值给 amount 。 var amountB = [amount]; // 声明一个 变量 叫 amountB 是一个数组,用 amount 初始化 一个元素 var amountS = [amount]; // 声明一个 变量 叫 amountS ... if (typeof (AmountType) !== 'undefined' && AmountType == 1) // { // 按自定义量 ,订单大小类型 这个界面参数如果不是未定义的, //并且 AmountType 在界面上设定为 自定义量,即AmountType 值为 1 (下拉框的索引) for (var idx = 0; idx < AllNum; idx++) // { // AllNum 总数量。 如果是设置自定义量, 根据总数量 循环一定次数 给amountB/amountS 即买卖单量数组赋值 amountB[idx] = BAmountOnce; // 使用界面参数 给买单量数组 赋值 amountS[idx] = SAmountOnce; // ... 给卖单... } }// else // { // 其它 for (var idx = 1; idx < AllNum; idx++) // { // 根据网格总数量 循环。 switch (AmountCoefficient[0]) // { // 根据界面参数 差量 这个字符串的 第一个 字符,即 AmountCoefficient[0] 是 '+'、'-'、'*'、'/' case '+': // 根据 界面参数 进行 构造 下单量加法递增的网格。 amountB[idx] = amountB[idx - 1] + parseFloat(AmountCoefficient.substring(1)); break; case '-': // ... amountB[idx] = amountB[idx - 1] - parseFloat(AmountCoefficient.substring(1)); break; case '*': amountB[idx] = amountB[idx - 1] * parseFloat(AmountCoefficient.substring(1)); break; case '/': amountB[idx] = amountB[idx - 1] / parseFloat(AmountCoefficient.substring(1)); break; } amountB[idx] = _N(amountB[idx], AmountDot); // 买单 、买单 量相同,处理好数据小数位。 amountS[idx] = amountB[idx]; // 赋值。 } } if (FirstPriceAuto) // { // 如果界面参数设置了 首价格自动 为 true ,执行 if 花括号内代码。 FirstPrice = BuyFirst ? _N(ticker.Buy - PriceGrid, Precision) : _N(ticker.Sell + PriceGrid, Precision); // 界面参数 FirstPrice 根据 BuyFirst全局变量(声明初始为true,在main开始已经根据OpType赋值)设定第一个价格,用此刻行情 ticker 和 界面参数 PriceGrid 价格间距去设定。 } // Initialize fish table 初始化网格 var fishTable = // {}; // 声明一个 网格对象 var uuidTable = // {}; // 识别码 表格对象 var needStocks = 0; // 所需币数 变量 var needMoney = 0; // 所需 钱 变量 var actualNeedMoney = 0; // 实际需要的 钱 var actualNeedStocks = 0; // 实际需要的 币 var notEnough = false; // 资金不足 标记变量, 初始设置为false var canNum = 0; // 可用 网格 for (var idx = 0; idx < AllNum; idx++) // { // 根据 网格数 AllNum 去遍历 构造。 var price = _N((BuyFirst ? FirstPrice - (idx * PriceGrid) : FirstPrice + (idx * PriceGrid)), Precision); // 遍历构造时,当前的索引idx 的 价格 设置 根据 BuyFirst 去设置。 每个索引价格 之间的间距 为 PriceGrid . needStocks += amountS[idx]; // 卖出所需 币数 随着 循环逐步 累计。(由 卖单量数组逐个累计到 needStocks) needMoney += price * amountB[idx]; // 买入所需 钱数 随着 循环逐步 累计。(.... 买单量数组逐个累计...) if (BuyFirst) // { // 处理 先买 if (_N(needMoney) <= _N(account.Balance)) // { // 如果 网格所需的钱 小于 账户上的可用钱数 actualNeedMondy = needMoney; // 赋值给 实际所需要的钱数 actualNeedStocks = needStocks; // 赋值给 实际所需要的币数 该出有些问题? canNum++; // 累计 可用网格数 }// else // { // _N(needMoney) <= _N(account.Balance) 该条件不满足,则设置 资金不足标记变量 为 true notEnough = true; } }// else // { // 处理 先卖 if (_N(needStocks) <= _N(account.Stocks)) // { // 检测 所需币数 是不是 小于 账户 可用币数 actualNeedMondy = needMoney; // 赋值 actualNeedStocks = needStocks; canNum++; // 累计可用网格数 }// else // { notEnough = true; // 不满足资金条件 ,就设置 true } } fishTable[idx] = STATE_WAIT_OPEN; // 根据当前索引idx,设置网格对象的idx成员(网格结点)的状态,初始为STATE_WAIT_OPEN(等待开仓) uuidTable[idx] = -1; // 编号对象 也根据当前 idx 初始化 自己的idx 值(对应 fishTable 的节点)为 -1 } if (!EnableAccountCheck && (canNum < AllNum)) // { // 如果不启用资金检验, 并且 可开 节点 小于 界面参数设置的网格数量(节点总数)时。 Log("警告, 当前资金只可做", canNum, "个网格, 全网共需", (BuyFirst ? needMoney : needStocks), "请保持资金充足"); // Log 输出 警告信息。 canNum = AllNum; // 更新可开数量 为界面参数的设置 } if (BuyFirst) // { // 先买 if (EnableProtectDiff && (FirstPrice - ticker.Sell) > ProtectDiff) // { // 开启差价保护 并且 入市价格 减去 此刻卖一 大于 入市差价保护 throw "首次买入价比市场卖1价高" + _N(FirstPrice - ticker.Sell, Precision) + ' 元'; // 抛出错误 信息。 }// else if (EnableAccountCheck && account.Balance < _N(needMoney)) // { // 如果启用资金检验 并且 账户 可用钱数 小于 网格所需资金钱数。 if (fishCount == 1) // { // 如果是第一次撒网 throw "资金不足, 需要" + _N(needMoney) + "元"; // 抛出错误 资金不足 }// else // { Log("资金不足, 需要", _N(needMoney), "元, 程序只做", canNum, "个网格 #ff0000"); // 如果不是 第一次 撒网, 输出提示信息。 } }// else // { // 其他情况, 没有开启 资金检验 、差价保护 等 Log('预计动用资金: ', _N(needMoney), "元"); // 输出 预计动用资金。 } }// else // { // 先卖, 一下类似 先买 if (EnableProtectDiff && (ticker.Buy - FirstPrice) > ProtectDiff) // { throw "首次卖出价比市场买1价高 " + _N(ticker.Buy - FirstPrice, Precision) + ' 元'; }// else if (EnableAccountCheck && account.Stocks < _N(needStocks)) // { if (fishCount == 1) // { throw "币数不足, 需要 " + _N(needStocks) + " 个币"; }// else // { Log("资金不足, 需要", _N(needStocks), "个币, 程序只做", canNum, "个网格 #ff0000"); } }// else // { Log('预计动用币数: ', _N(needStocks), "个, 约", _N(needMoney), "元"); } } var trader = new Trader(); // 构造一个 Trader 对象, 赋值给 此处声明的 trader 变量。 var OpenFunc = BuyFirst ? exchange.Buy : exchange.Sell; // 根据 是否先买后卖 ,设定开仓函数OpenFunc 是 引用 exchange.Buy 还是 exchange.Sell var CoverFunc = BuyFirst ? exchange.Sell : exchange.Buy; // 同上 if (EnableDynamic) // { // 根据界面参数 EnableDynamic (是否动态挂单) 是否开启, 去再次 设定 OpenFunc/CoverFunc OpenFunc = BuyFirst ? trader.Buy : trader.Sell; // 引用 trader 对象的 成员函数 Buy 用于 动态挂单 (主要是由于一些交易所 限制挂单数量,所以就需要虚拟动态挂单) CoverFunc = BuyFirst ? trader.Sell : trader.Buy; // 同上 } var ts = new Date(); // 创建此刻时间对象(赋值给ts),用于记录此刻时间。 var preMsg = ""; // 声明一个 变量 用于记录 上次信息, 初始 空字符串 var profitMax = 0; // 最大收益 while (true) // { // 网格 撒网后的 主要 逻辑 var now = new Date(); // 记录 当前循环 开始的时的时间 var table = null; // 声明一个 变量 if (now.getTime() - ts.getTime() > 5000) // { // 计算当前 时间 now 和 记录的时间 ts 之间的差值 是否大于 5000 毫秒 if (typeof (GetCommand) == 'function' && GetCommand() == "收网") // { // 检测是否 接收到 策略 交互控件 命令 “收网”,停止并平衡到初始状态 Log("开始执行命令进行收网操作"); // 输出 信息 balanceAccount(orgAccount, InitAccount); // 执行平衡函数 ,平衡币数 到初始状态 return false; // 本次 撒网函数 fishing 返回 false } ts = now; // 用当前时间 now 更新 ts,用于下次比对时间 var nowAccount = _C(exchange.GetAccount); // 声明 nowAccount 变量 并初始为当前最新 账户信息。 var ticker = _C(exchange.GetTicker); // 声明 ticker 变量 并初始为当前行情信息 if (EnableDynamic) // { // 如果开启动态挂单 trader.Poll(ticker, DynamicMax); // 调用 trader 对象的 Poll 函数 ,根据 当前 ticker 行情,和 界面参数 DynamicMax(订单失效距离)检测 并 处理 所有订单。 } var amount_diff = (nowAccount.Stocks + nowAccount.FrozenStocks) - (InitAccount.Stocks + InitAccount.FrozenStocks); // 计算当前的 币差 var money_diff = (nowAccount.Balance + nowAccount.FrozenBalance) - (InitAccount.Balance + InitAccount.FrozenBalance); // 计算当前的 钱差 var floatProfit = _N(money_diff + (amount_diff * ticker.Last)); // 计算 当前 本次撒网 的浮动盈亏 var floatProfitAll = _N((nowAccount.Balance + nowAccount.FrozenBalance - orgAccount.Balance - orgAccount.FrozenBalance) + ((nowAccount.Stocks + nowAccount.FrozenStocks - orgAccount.Stocks - orgAccount.FrozenStocks) * ticker.Last)); // 计算 总体的浮动盈亏 var isHold = Math.abs(amount_diff) >= exchange.GetMinStock(); // 如果 此刻 币差 绝对值 大于 交易所最小 交易量 ,代表已经持仓 if (isHold) // { // 已经持仓 则执行 setBusy() 函数,该函数会给 LastBusy 更新时间。 setBusy(); // 即 开仓后开始 启动开仓机制。 } profitMax = Math.max(floatProfit, profitMax); // 刷新 最大浮动盈亏 if (EnableAccountCheck && EnableStopLoss) // { // 如果启动账户检测 并且 启动 止损 if ((profitMax - floatProfit) >= StopLoss) // { // 如果 最大浮动盈亏 减去 当前浮动盈亏 大于等于 最大浮动亏损值,则执行 花括号内代码 Log("当前浮动盈亏", floatProfit, "利润最高点: ", profitMax, "开始止损"); // 输出信息 balanceAccount(orgAccount, InitAccount); // 平衡账户 if (StopLossMode == 0) // { // 根据 止损模式 处理, 如果 StopLossMode 等于 0 ,即 止损后退出程序。 throw "止损退出"; // 抛出错误 “止损退出” 策略停止。 }// else // { return true; // 除了 止损后退出模式, 即 : 止损后重新撒网。 } } } if (EnableAccountCheck && EnableStopWin) // { // 如果开启了 检测账户 并且 开启了 止盈 if (floatProfit > StopWin) // { // 如果 浮动盈亏 大于 止盈 Log("当前浮动盈亏", floatProfit, "开始止盈"); // 输出日志 balanceAccount(orgAccount, InitAccount); // 平衡账户 恢复初始 (止盈) if (StopWinMode == 0) // { // 根据止盈模式 处理。 throw "止盈退出"; // 止盈后退出 }// else // { return true; // 止盈后 返回 true , 继续撒网 } } } var distance = 0; // 声明 一个 变量 用来 记录 距离 if (EnableAccountCheck && AutoMove) // { // 如果开启 账户检测 并且 网格自动移动 if (BuyFirst) // { // 如果是 先买后卖 distance = ticker.Last - FirstPrice; // 给 distance 赋值 : 当前的价格 减去 首价格,算出距离 }// else // { // 其他情况 : 先卖后买 distance = FirstPrice - ticker.Last; // 给 distance 赋值 : 首价格 减去 当前价格,算出距离 } var refish = false; // 是否重新撒网 标记变量 if (!isHold && isTimeout()) // { // 如果没有持仓(isHold 为 false) 并且 超时(isTimeout 返回 true) Log("空仓过久, 开始移动网格"); refish = true; // 标记 重新撒网 } if (distance > MaxDistance) // { // 如果 当前 的距离 大于 界面参数设定的最大距离, 标记 重新撒网 Log("价格超出网格区间过多, 开始移动网格, 当前距离: ", _N(distance, Precision), "当前价格:", ticker.Last); refish = true; } if (refish) // { // 如果 refish 是 true ,则执行 平衡函数 balanceAccount(orgAccount, InitAccount); return true; // 本次 撒网函数 返回 true } } var holdDirection, holdAmount = "--", // 声明 三个 变量,持仓方向、持仓数量、持仓价格 holdPrice = "--"; if (isHold) // { // 持仓时 if (RestoreProfit && ProfitAsOrg) // { // 如果 开启 恢复上次盈利 并且 上次盈利算入均价 if (BuyFirst) // { // 如果是先买后卖 money_diff += LastProfit; // 把上次盈利 加入 money_diff ,即 上次收益 折合入 钱差(在先买的情况,钱差为负值,即花费的),折合入开仓成本。 }// else // { // 如果是先卖后买 money_diff -= LastProfit; // 先卖后买 钱差 为 正值 , why - ? } } // 处理先买后卖 holdAmount = amount_diff; // 币差 赋值 给持仓数量 (此刻币差 即 持仓) holdPrice = (-money_diff) / amount_diff; // 用 钱差 除以 币差 算出 持仓均价, // 注意 : 如果 money_diff 为 负值 ,则amount_diff 一定为正值,所以一定要在 money_diff 前加 负号,这样算出的价格才是 正数 // 处理先卖后买 if (!BuyFirst) // { // 如果是 先卖后买 则触发 更新 持仓量 和 持仓均价 holdAmount = -amount_diff; // 币差为负数 ,所以取反 holdPrice = (money_diff) / -amount_diff; // 计算持仓均价。 } holdAmount = _N(holdAmount, 4); // 持仓量,保留4位小数。 holdPrice = _N(holdPrice, Precision); // 持仓均价, 保留 Precision 位小数。 holdDirection = BuyFirst ? "多" : "空"; // 根据 先买后卖 或者 先卖后买 给 holdDirection 赋值 多 或者 空 }// else // { // 如果 isHold 为false ,给holdDirection 赋值 "--" holdDirection = "--"; } table = // { // 给声明 的 table 变量 赋值一个 对象,用于在 发明者量化 机器人 状态栏上显示 表格信息 type: 'table', // 详见 API 文档 LogStatus 函数, 这里给 type 属性 初始化 'table' 用于在状态栏显示成表格 title: '运行状态', // 表格的 标题 cols: ['动用资金', '持有仓位', '持仓大小', '持仓均价', '总浮动盈亏', '当前网格盈亏', '撒网次数', '网格偏移', '真实委托', '最新币价'], // 表格的 列名 rows: [ // 表格的 逐行的数据 [_N(actualNeedMondy, 4), holdDirection, holdAmount, holdPrice, _N(floatProfitAll, 4) + ' ( ' + _N(floatProfitAll * 100 / actualNeedMondy, 4) + ' % )', floatProfit, fishCount, (AutoMove && distance > 0) ? ((BuyFirst ? "向上" : "向下") + "偏离: " + _N(distance) + " 元") : "--", trader.RealLen(), ticker.Last] // 一行数据 ] }; } // 每间隔 5 秒处理 一些任务, 并更新 机器人状态栏 表格对象 table var orders = _C(trader.GetOrders); // 获取 所有未完成的订单 if (table) // { // 如果 table 已经被 赋值表格对象 if (!EnableDynamic) // { // 如果没有开启动态挂单 table.rows[0][8] = orders.length; // 在状态栏 表格 第一行 第9列 位置 更新 挂单数组的长度 } LogStatus('`' + JSON.stringify(table) + '`'); // 调用 发明者量化 平台 API LogStatus 显示 设置的状态栏表格 } for (var idx = 0; idx < canNum; idx++) // { // 遍历 可用的 网格节点数量。 var openPrice = _N((BuyFirst ? FirstPrice - (idx * PriceGrid) : FirstPrice + (idx * PriceGrid)), Precision); // 随着 节点 索引 idx 遍历,构造每个节点的 开仓价 (方向由 先买后卖,或者先卖后买 决定) var coverPrice = _N((BuyFirst ? openPrice + PriceDiff : openPrice - PriceDiff), Precision); // 开仓平仓价差,即 每个节点的盈利空间 var state = fishTable[idx]; // 赋值 渔网 节点的状态 var fishId = uuidTable[idx]; // 编号 // 此处判断作用为: 过滤 未完成的订单 if (hasOrder(orders, fishId)) // { // 如果 所有未完成订单,即挂单数组 中有ID为 fishId 的订单 continue; // 跳过本次循环 继续循环 } if (fishId != -1 && IsSupportGetOrder) // { // 网格 节点 id 不等于 初始值,即下过订单,并且 交易所支持 GetOrder var order = trader.GetOrder(fishId); // 获取 该 fishId 号 的订单 // 此处判断作用为: 过滤 没有找到订单 的 网格节点,以下判断(state == STATE_WAIT_COVER) 等等 的逻辑不会触发 if (!order) // { // 如果 !order 为真 即获取订单失败 Log("获取订单信息失败, ID: ", fishId); // 输出日志 continue; // 跳过本次循环 继续循环 } // 此处判断作用为: 过滤 处于挂起状态,未成交,或者 未完全成交的 网格节点, 以下判断(state == STATE_WAIT_COVER) 等等 的逻辑不会触发 if (order.Status == ORDER_STATE_PENDING) // { // 如果订单状态 是在交易所 挂起状态 //Log("订单状态为未完成, ID: ", fishId); continue; // 跳过本次循环 继续循环 } } if (state == STATE_WAIT_COVER) // { // 如果 当前节点 状态是 等待平仓 var coverId = CoverFunc(coverPrice, (BuyFirst ? amountS[idx] : amountB[idx]), (BuyFirst ? '完成买单:' : '完成卖单:'), openPrice, '量:', (BuyFirst ? amountB[idx] : amountS[idx])); // 调用 平仓 函数 CoverFunc 挂出 平仓单 if (typeof (coverId) === 'number' || typeof (coverId) === 'string') // { // 判断 如果 平仓函数 返回的 Id 为 数值(由 发明者量化 API 直接返回) 或者 字符串(由 trader 对象的 Buy/Sell函数返回) fishTable[idx] = STATE_WAIT_CLOSE; // 已经挂出 平仓单, 更新状态为 : STATE_WAIT_CLOSE 即等待 节点任务完成 uuidTable[idx] = coverId; // 把 订单号 储存在 uuidTable 对应的 idx 位置上。 } }// else if (state == STATE_WAIT_OPEN || state == STATE_WAIT_CLOSE) // { // 如果状态是 等待开仓 或者 等待完成 var openId = OpenFunc(openPrice, BuyFirst ? amountB[idx] : amountS[idx]); // 下开仓单。 if (typeof (openId) === 'number' || typeof (openId) === 'string') // { // 判断是否下单成功 fishTable[idx] = STATE_WAIT_COVER; // 更新状态 为等待平仓 uuidTable[idx] = openId; // 记录当前 节点 订单ID if (state == STATE_WAIT_CLOSE) // { // 如果是等待完成 (开仓订单下了后 才会触发) ProfitCount++; // 累计盈利次数 var account = _C(exchange.GetAccount); // 获取当前账户信息 var ticker = _C(exchange.GetTicker); // 获取当前行情信息 var initNet = _N(((InitAccount.Stocks + InitAccount.FrozenStocks) * ticker.Buy) + InitAccount.Balance + InitAccount.FrozenBalance, 8); // 计算 初始 资产 净值 var nowNet = _N(((account.Stocks + account.FrozenStocks) * ticker.Buy) + account.Balance + account.FrozenBalance, 8); // 计算 当前 资产 净值 var actualProfit = _N(((nowNet - initNet)) * 100 / initNet, 8); // 计算 收益率 if (AmountType == 0) // { // 根据 买卖同量 , 自定义量 不同的处理。 var profit = _N((ProfitCount * amount * PriceDiff) + LastProfit, 8); // 计算: 所有盈利节点的 盈亏 和 上次撒网盈亏 之和 即 总盈亏 Log((BuyFirst ? '完成卖单:' : '完成买单:'), coverPrice, '量:', (BuyFirst ? amountS[idx] : amountB[idx]), '平仓收益', profit); // 输出 订单完成信息 }// else // { Log((BuyFirst ? '完成卖单:' : '完成买单:'), coverPrice, '量:', (BuyFirst ? amountS[idx] : amountB[idx])); } } } } } Sleep(CheckInterval); // 网格逻辑 主要 while 循环检测, 每次 暂停一定时间 CheckInterval 即:检测间隔 } return true; // 本次撒网完成 返回 true } function main() // { // 策略主函数,程序从这里开始执行。 if (ResetData) // { // RestData 为界面参数, 默认 true , 控制 启动时 是否清空所有数据。默认全部清空。 LogProfitReset(); // 执行 API LogProfitReset 函数,清空 所有收益。 LogReset(); // 执行 API LogReset 函数, 清空 所有日志。 } // exchange.SetMaxDigits(Precision) // 已废弃,使用 exchange.SetPrecision 代替。 exchange.SetPrecision(Precision, 3) // exchange.SetPrecision(2, 3); // 设置价格小数位精度为2位, 品种下单量小数位精度为3位 // Precision 为界面参数。 if (typeof (AmountType) === 'undefined') // { // 订单 数量类型, 0:“买卖同量” , 1:“自定义量” , 检测 如果该参数是 未定义的,默认设置 0 。 AmountType = 0; // typeof 会 检测 AmountType 的类型, 如果是 undefined 即 “未定义” ,则给 AmountType 赋值 0。 } if (typeof (AmountDot) === 'undefined') // { // 订单量 小数点 最长位数 AmountDot 如果是 未定义的, 设置 AmountDot 为 3 。 AmountDot = 3; // 其实已经由 exchange.SetPrecision(Precision, 3) 设置过了,在底层会截断处理。 } if (typeof (EnableDynamic) === 'undefined') // { // 检测 是否 开启动态挂单 参数, 如果 EnableDynamic 是未定义的, 设置 为 false 即 不开启。 EnableDynamic = false; } if (typeof (AmountCoefficient) === 'undefined') // { // 如果未定义, 默认设置 "*1" AmountCoefficient = "*1"; } if (typeof (EnableAccountCheck) === 'undefined') // {// 如果未定义, 启用资金检验 参数 设置为 true ,即 开启。 EnableAccountCheck = true; } BuyFirst = (OpType == 0); // 根据 OpType 的设置 去 给BuyFirst 赋值, OpType 设置网格类型, 0: 先买后卖, 1: 先卖后买 IsSupportGetOrder = exchange.GetName().indexOf('itstamp') == -1; // 检测 交易所 名称, 如果是 Bitstamp 则提醒 if (!IsSupportGetOrder) // { Log(exchange.GetName(), "不支持GetOrder, 可能影响策略稳定性."); } SetErrorFilter("502:|503:|S_U_001|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|refused|EOF|When"); // SetErrorFilter 过滤错误信息 exchange.SetRate(1); Log('已经禁用汇率转换, 当前货币为', exchange.GetBaseCurrency()); // 禁用汇率转换 if (!RestoreProfit) // { // 恢复上次盈利 若果是 false 则 给 LastProfit 赋值 0 , 即不恢复。 LastProfit = 0; } var orgAccount = _C(exchange.GetAccount); // 获取账户信息, 此处记录 策略开始运行时的 初始账户信息 ,用于 计算一些收益,如: 总体浮动盈亏 等。本策略有几个参数 都是 该变量传入。 var fishCount = 1; // 撒网次数 初始1 while (true) // { // 策略 主循环 if (!fishing(orgAccount, fishCount)) // { // 撒网函数 fishing break; } fishCount++; // 撒网次数 累计 Log("第", fishCount, "次重新撒网..."); // 输出 撒网信息。 FirstPriceAuto = true; // 重置 首价格自动 为true Sleep(1000); // 轮询间隔 1000毫秒 } }