Grids can be customized Buy and sell first: The grid starts from the initial price and hangs the invoice downwards, with each purchase interval The price interval of the grid This parameter, the number of pending invoices is "single number", hangs enough The total number of pending invoices, there is an arbitrary invoice transaction, after the program adds on the basis of the purchase price The price of the value of this parameter is the price of the pending bid, sell, sell, after the sale, re-pending the purchase price of the grid Buy before you sell: Operate the opposite way.
The biggest risk of the strategy is the one-sided market, where prices fluctuate outside the grid.
Grids with automatic stop loss and mobile functions
// Grid can customize direction // Basic trading operation: buy first and then sell. // The grid will start to send buying order at the price that below the first price, which is the price follow // by the first price (second latest buying price, third latest buying price…and so on). Each buying order is separated by // the "price interval" parameter. The number of pending orders is "single quantity", and will send the order total until // the "total quantity" is filled. // After any buying order is completed, the program will be on the basis of the buying price, add the price of the "price // difference" parameter to the sell price, after the order has been sold, and then re-start the progress of this // grid strategy (checking, place order, wait until it executed, sell) // Selling short first and then buy to cover: the operation is just the opposite // The biggest risk of this strategy is when the market trend is unilateral moving, and the price fluctuations are exceeding the grid. // The following code has made the grid with automatic stop loss and movement function. // Comments: // The strategy uses a virtual pending order design, which provides a great deal of processing for the exchange to limit the number // of pending orders, and solves the problem flexibly. // The grid logic is flexible in design and clever in structure. // Profit and loss calculation, each numerical statistical algorithm can be used for reference, and each condition detection design // is rigorous. (for minimize the possibility of BUG) // The source code is very worth learning. // Source Code: /* Interface parameters (shown as global variables in the code) OpType Grid Direction Drop-down box (selected) Buy first then sell | Sell first then buy FirstPriceAuto initial price automatic boolean (true/false) true FirstPrice@!FirstPriceAuto initial price numerical (number) 100 AllNum total number numerical (number) 10 PriceGrid Price Interval numerical (number) 1 PriceDiff spread numerical (number) 2 AmountType order size drop-down box (selected) buy and sell the same amount | custom amount AmountOnce@AmountType==0 Single transaction quantity numerical (number) 0.1 BAmountOnce@AmountType==1 Buying Order Size numerical (number) 0.1 SAmountOnce@AmountType==1 Selling order size numerical (number) 0.1 AmountCoefficient@AmountType==0 Quantity difference String (string) *1 AmountDot The decimal point numerical (number) 3 EnableProtectDiff Turn on spread protection Boolean (true/false) false ProtectDiff@EnableProtectDiff Entry spread Price Protection numerical (number) 20 CancelAllWS stop cancels all pending orders Boolean (true/false) true CheckInterval polling interval number numerical (number) 2000 Interval failure retry interval numerical (number) 1300 RestoreProfit restores last profit Boolean (true/false) false LastProfit@RestoreProfit Last Profit numerical (number) 0 ProfitAsOrg@RestoreProfit Last profit counted as average price Boolean (true/false) false EnableAccountCheck enable balance verification Boolean (true/false) true EnableStopLoss@EnableAccountCheck open Stop Loss Boolean (true/false) false StopLoss@EnableStopLoss maximum floating loss numerical (number) 100 StopLossMode@EnableStopLoss Post-stop loss operation Drop-down box (selected) Recycle and exit | Recycle and re-cast EnableStopWin@EnableAccountCheck Turn on Take Profit Boolean (true/false) false StopWin@EnableStopWin Maximum floating profit Number type (number) 120 StopWinMode@EnableStopWin post-take profit operation drop-down box (selected) Recycle and exit | Recycle and re-cast AutoMove@EnableAccountCheck auto Move Boolean (true/false) false MaxDistance@AutoMove maximum distance numerical (number) 20 MaxIdle@AutoMove maximum idle (seconds) numerical (number) 7200 EnableDynamic Turns on dynamic pending orders Boolean (true/false) false DynamicMax@EnableDynamic order expiration distance Number (number) 30 ResetData clears all data at startup Boolean (true/false) true Precision price decimal length numerical (number) 5 */ function hasOrder(orders, orderId) { for (var i = 0; i < orders.length; i++) { if (orders[i].Id == orderId) { return true; } } return false; } function cancelPending() { var ret = false; while (true) { if (ret) { Sleep(Interval); } var orders = _C(exchange.GetOrders); if (orders.length == 0) { break; } for (var j = 0; j < orders.length; j++) { exchange.CancelOrder(orders[j].Id, orders[j]); ret = true; } } return ret; } function valuesToString(values, pos) { var result = ''; if (typeof(pos) === 'undefined') { pos = 0; } for (var i = pos; i < values.length; i++) { if (i > pos) { result += ' '; } if (values[i] === null) { result += 'null'; } else if (typeof(values[i]) == 'undefined') { result += 'undefined'; } else { switch (values[i].constructor.name) { case 'Date': case 'Number': case 'String': case 'Function': result += values[i].toString(); break; default: result += JSON.stringify(values[i]); break; } } } return result; } function Trader() { var vId = 0; var orderBooks = []; var hisBooks = []; var orderBooksLen = 0; this.Buy = function(price, amount, extra) { if (typeof(extra) === 'undefined') { extra = ''; } else { extra = valuesToString(arguments, 2); } vId++; var orderId = "V" + vId; orderBooks[orderId] = { Type: ORDER_TYPE_BUY, Status: ORDER_STATE_PENDING, Id: 0, Price: price, Amount: amount, Extra: extra }; orderBooksLen++; return orderId; }; this.Sell = function(price, amount, extra) { 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); for (orderId in orderBooks) { var order = orderBooks[orderId]; if (order.Status !== ORDER_STATE_PENDING) { continue; } var found = false; for (var i = 0; i < orders.length; i++) { if (orders[i].Id == order.Id) { found = true; break; } } if (!found) { orders.push(orderBooks[orderId]); } } return orders; } this.GetOrder = function(orderId) { if (typeof(orderId) === 'number') { return exchange.GetOrder(orderId); } if (typeof(hisBooks[orderId]) !== 'undefined') { return hisBooks[orderId]; } if (typeof(orderBooks[orderId]) !== 'undefined') { return orderBooks[orderId]; } return null; }; this.Len = function() { return orderBooksLen; }; this.RealLen = function() { var n = 0; for (orderId in orderBooks) { if (orderBooks[orderId].Id > 0) { n++; } } return n; }; this.Poll = function(ticker, priceDiff) { var orders = _C(exchange.GetOrders); for (orderId in orderBooks) { var order = orderBooks[orderId]; if (order.Id > 0) { var found = false; for (var i = 0; i < orders.length; i++) { if (order.Id == orders[i].Id) { found = true; } } if (!found) { order.Status = ORDER_STATE_CLOSED; hisBooks[orderId] = order; delete(orderBooks[orderId]); orderBooksLen--; continue; } } var diff = _N(order.Type == ORDER_TYPE_BUY ? (ticker.Buy - order.Price) : (order.Price - ticker.Sell)); var pfn = order.Type == ORDER_TYPE_BUY ? exchange.Buy : exchange.Sell; if (order.Id == 0 && diff <= priceDiff) { var realId = pfn(order.Price, order.Amount, order.Extra + "(距离: " + diff + (order.Type == ORDER_TYPE_BUY ? (" 买一: " + ticker.Buy) : (" 卖一: " + ticker.Sell))+")"); if (typeof(realId) === 'number') { order.Id = realId; } } else if (order.Id > 0 && diff > (priceDiff + 1)) { var ok = true; do { 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)); Sleep(200); orders = _C(exchange.GetOrders); for (var i = 0; i < orders.length; i++) { if (orders[i].Id == order.Id) { ok = false; } } } while (!ok); order.Id = 0; } } }; } function balanceAccount(orgAccount, initAccount) { cancelPending(); var nowAccount = _C(exchange.GetAccount); var slidePrice = 0.2; var ok = true; while (true) { var diff = _N(nowAccount.Stocks - initAccount.Stocks); if (Math.abs(diff) < MinStock) { break; } var depth = _C(exchange.GetDepth); var books = diff > 0 ? depth.Bids : depth.Asks; var n = 0; var price = 0; for (var i = 0; i < books.length; i++) { n += books[i].Amount; if (n >= Math.abs(diff)) { price = books[i].Price; break; } } var pfn = diff > 0 ? exchange.Sell : exchange.Buy; var amount = Math.abs(diff); var price = diff > 0 ? (price - slidePrice) : (price + slidePrice); Log("开始平衡", (diff > 0 ? "卖出" : "买入"), amount, "个币"); if (diff > 0) { amount = Math.min(nowAccount.Stocks, amount); } else { amount = Math.min(nowAccount.Balance / price, amount); } if (amount < MinStock) { Log("资金不足, 无法平衡到初始状态"); ok = false; break; } pfn(price, amount); Sleep(1000); cancelPending(); nowAccount = _C(exchange.GetAccount); } if (ok) { LogProfit(_N(nowAccount.Balance - orgAccount.Balance)); Log("平衡完成", nowAccount); } } var STATE_WAIT_OPEN = 0; var STATE_WAIT_COVER = 1; var STATE_WAIT_CLOSE = 2; var ProfitCount = 0; var BuyFirst = true; var IsSupportGetOrder = true; var LastBusy = 0; function setBusy() { LastBusy = new Date(); } function isTimeout() { if (MaxIdle <= 0) { return false; } var now = new Date(); if (((now.getTime() - LastBusy.getTime()) / 1000) >= MaxIdle) { LastBusy = now; return true; } return false; } function onexit() { if (CancelAllWS) { Log("正在退出, 尝试取消所有挂单"); cancelPending(); } Log("策略成功停止"); Log(_C(exchange.GetAccount)); } function fishing(orgAccount, fishCount) { setBusy(); var account = _C(exchange.GetAccount); Log(account); var InitAccount = account; var ticker = _C(exchange.GetTicker); var amount = _N(AmountOnce); var amountB = [amount]; var amountS = [amount]; if (typeof(AmountType) !== 'undefined' && AmountType == 1) { for (var idx = 0; idx < AllNum; idx++) { amountB[idx] = BAmountOnce; amountS[idx] = SAmountOnce; } } else { for (var idx = 1; idx < AllNum; idx++) { switch (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) { FirstPrice = BuyFirst ? _N(ticker.Buy - PriceGrid, Precision) : _N(ticker.Sell + PriceGrid, Precision); } // Initialize fish table var fishTable = {}; var uuidTable = {}; var needStocks = 0; var needMoney = 0; var actualNeedMoney = 0; var actualNeedStocks = 0; var notEnough = false; var canNum = 0; for (var idx = 0; idx < AllNum; idx++) { var price = _N((BuyFirst ? FirstPrice - (idx * PriceGrid) : FirstPrice + (idx * PriceGrid)), Precision); needStocks += amountS[idx]; needMoney += price * amountB[idx]; if (BuyFirst) { if (_N(needMoney) <= _N(account.Balance)) { actualNeedMondy = needMoney; actualNeedStocks = needStocks; canNum++; } else { notEnough = true; } } else { if (_N(needStocks) <= _N(account.Stocks)) { actualNeedMondy = needMoney; actualNeedStocks = needStocks; canNum++; } else { notEnough = true; } } fishTable[idx] = STATE_WAIT_OPEN; uuidTable[idx] = -1; } if (!EnableAccountCheck && (canNum < AllNum)) { Log("警告, 当前资金只可做", canNum, "个网格, 全网共需", (BuyFirst ? needMoney : needStocks), "请保持资金充足"); 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(); var OpenFunc = BuyFirst ? exchange.Buy : exchange.Sell; var CoverFunc = BuyFirst ? exchange.Sell : exchange.Buy; if (EnableDynamic) { OpenFunc = BuyFirst ? trader.Buy : trader.Sell; CoverFunc = BuyFirst ? trader.Sell : trader.Buy; } var ts = new Date(); var preMsg = ""; var profitMax = 0; while (true) { var now = new Date(); var table = null; if (now.getTime() - ts.getTime() > 5000) { if (typeof(GetCommand) == 'function' && GetCommand() == "收网") { Log("开始执行命令进行收网操作"); balanceAccount(orgAccount, InitAccount); return false; } ts = now; var nowAccount = _C(exchange.GetAccount); var ticker = _C(exchange.GetTicker); if (EnableDynamic) { 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) >= MinStock; if (isHold) { setBusy(); } profitMax = Math.max(floatProfit, profitMax); if (EnableAccountCheck && EnableStopLoss) { if ((profitMax - floatProfit) >= StopLoss) { Log("当前浮动盈亏", floatProfit, "利润最高点: ", profitMax, "开始止损"); balanceAccount(orgAccount, InitAccount); if (StopLossMode == 0) { throw "止损退出"; } else { return true; } } } if (EnableAccountCheck && EnableStopWin) { if (floatProfit > StopWin) { Log("当前浮动盈亏", floatProfit, "开始止盈"); balanceAccount(orgAccount, InitAccount); if (StopWinMode == 0) { throw "止盈退出"; } else { return true; } } } var distance = 0; if (EnableAccountCheck && AutoMove) { if (BuyFirst) { distance = ticker.Last - FirstPrice; } else { distance = FirstPrice - ticker.Last; } var refish = false; if (!isHold && isTimeout()) { Log("空仓过久, 开始移动网格"); refish = true; } if (distance > MaxDistance) { Log("价格超出网格区间过多, 开始移动网格, 当前距离: ", _N(distance, Precision), "当前价格:", ticker.Last); refish = true; } if (refish) { balanceAccount(orgAccount, InitAccount); return true; } } var holdDirection, holdAmount = "--", holdPrice = "--"; if (isHold) { if (RestoreProfit && ProfitAsOrg) { if (BuyFirst) { money_diff += LastProfit; } else { money_diff -= LastProfit; } } holdAmount = amount_diff; holdPrice = (-money_diff) / amount_diff; if (!BuyFirst) { holdAmount = -amount_diff; holdPrice = (money_diff) / -amount_diff; } holdAmount = _N(holdAmount, 4); holdPrice = _N(holdPrice, Precision); holdDirection = BuyFirst ? "多" : "空"; } else { holdDirection = "--"; } table = { 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] ] }; } var orders = _C(trader.GetOrders); if (table) { if (!EnableDynamic) { table.rows[0][8] = orders.length; } LogStatus('`' + JSON.stringify(table) + '`'); } for (var idx = 0; idx < canNum; idx++) { var openPrice = _N((BuyFirst ? FirstPrice - (idx * PriceGrid) : FirstPrice + (idx * PriceGrid)), Precision); var coverPrice = _N((BuyFirst ? openPrice + PriceDiff : openPrice - PriceDiff), Precision); var state = fishTable[idx]; var fishId = uuidTable[idx]; if (hasOrder(orders, fishId)) { continue; } if (fishId != -1 && IsSupportGetOrder) { var order = trader.GetOrder(fishId); if (!order) { Log("获取订单信息失败, ID: ", fishId); continue; } 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])); if (typeof(coverId) === 'number' || typeof(coverId) === 'string') { fishTable[idx] = STATE_WAIT_CLOSE; uuidTable[idx] = coverId; } } 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; 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); } return true; } function main() { if (ResetData) { LogProfitReset(); LogReset(); } exchange.SetPrecision(Precision, XPrecision) if (typeof(AmountType) === 'undefined') { AmountType = 0; } if (typeof(AmountDot) === 'undefined') { AmountDot = 3; } if (typeof(EnableDynamic) === 'undefined') { EnableDynamic = false; } if (typeof(AmountCoefficient) === 'undefined') { AmountCoefficient = "*1"; } if (typeof(EnableAccountCheck) === 'undefined') { EnableAccountCheck = true; } BuyFirst = (OpType == 0); IsSupportGetOrder = exchange.GetName().indexOf('itstamp') == -1; 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"); exchange.SetRate(1); Log('已经禁用汇率转换, 当前货币为', exchange.GetBaseCurrency()); if (!RestoreProfit) { LastProfit = 0; } var orgAccount = _C(exchange.GetAccount); var fishCount = 1; while (true) { if (!fishing(orgAccount, fishCount)) { break; } fishCount++; Log("第", fishCount, "次重新撒网..."); FirstPriceAuto = true; Sleep(1000); } }