The grid trading strategy

Author: , Created: 2018-08-23 13:45:27, Updated:

The grid trading strategy (www.fmz.com) The basic idea of grid trading is very straightforward. Instead of placing one trade, we place multiple trades forming a grid pattern. Usually these are entered as “stop” or “limit” orders around the current price level – but not always. I’ll explain this in more detail below, but that’s the basic idea.

What is grid trading and how does it work? Grid trading is a play on market volatility. There are two reasons why it’s favored by traders. The first is that it doesn’t “require” you to have a definitive prediction on the market direction.

The second is that it works well in volatile markets, where there isn’t a clear trend – these conditions are very common in the currency markets

Grid trading is a type of technical analysis trading that is based on movement within specific grid patterns. Grid trading is popular in foreign exchange trading. Overall the technique seeks to capitalize on normal price volatility in markets by placing buy and sell orders at certain regular intervals above and below a predefined base price. Such buy and sell orders, generally spaced at 10 or 15 units of intervals, create a trading grid.

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.

For more information,please see:

https://www.fmz.com/strategy/112633

Source Code:

// 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) {                           // Check if there is an order with order ID in parameter orders
    for (var i = 0; i < orders.length; i++) {                  // Traverse orders to check if there are same ids, if there are then return true
        if (orders[i].Id == orderId) {
            return true;
        }
    }
    return false;                                              // All traversed, no trigger if means haven't found the order with the ID orderId, return false
}


function cancelPending() {                                     // Cancel all pending order functions
    var ret = false;                                           // Set return success tag variable
    while (true) {                                             // while loop
        if (ret) {                                             // If ret is true then Sleep for a certain time
            Sleep(Interval);
        }
        var orders = _C(exchange.GetOrders);                   // Call the API to get the order information that the exchange did not executed.
        if (orders.length == 0) {                              // If an empty array is returned, the exchange has no unexecuted orders.
            break;                                             // Jump out of the while loop
        }

        for (var j = 0; j < orders.length; j++) {              // Traverse the unfinished order array and use orders[j].Id one by one to cancel the order based on the index j.
            exchange.CancelOrder(orders[j].Id, orders[j]);
            ret = true;                                        // Once there is a cancel operation, ret is assigned a value of true. Used to trigger above Sleep, wait for re-exchange.GetOrders detection 
        }
    }
    return ret;                                                // return ret
}

function valuesToString(values, pos) {                      // Value converted to a string
    var result = '';                                        // Declare an empty string result for return
    if (typeof(pos) === 'undefined') {                      // If the pos parameter is not passed, assign pos a value of 0.
        pos = 0;
    }
    for (var i = pos; i < values.length; i++) {             // Process values array according to the passed pos
        if (i > pos) {                                      // In addition to the first loop, add ' ' a space after the result string
            result += ' ';
        }
        if (values[i] === null) {                           // If values (function argument list array) the current index's element is null then result adds 'null' string
            result += 'null';
        } else if (typeof(values[i]) == 'undefined') {      // If it is undefined, add 'undefined'
            result += 'undefined';
        } else {                                            // Remaining type do switch detection separately
            switch (values[i].constructor.name) {           // Check the name property of the constructor of values[i], which is the type name
                case 'Date':
                case 'Number':
                case 'String':
                case 'Function':
                    result += values[i].toString();         // If it is a date type, a numeric type, a string type, or a function type, call its toString function and convert it to a string, then add
                    break;
                default:
                    result += JSON.stringify(values[i]);    // In other cases, use the JSON.stringify function to convert to a JSON string. Add to result 
                    break;
            }
        }
    }
    return result;                                          // return result
}

function Trader() {                                                 // Trader function, using closures.
    var vId = 0;                                                    // Order increment ID
    var orderBooks = [];                                            // Order book
    var hisBooks = [];                                              // Historical order book
    var orderBooksLen = 0;                                          // Order book length
    this.Buy = function(price, amount, extra) {                     // Buying function, parameters: price, quantity, extended information
        if (typeof(extra) === 'undefined') {                        // If the parameter extra is not passed in, ie typeof returns undefined 
            extra = '';                                             // Assign an empty string to extra
        } else {
            extra = valuesToString(arguments, 2);                   // The argument arguments passed when calling this.Buy function is passed to the valuesToString function.
        }
        vId++;                                                      // 
        var orderId = "V" + vId;                                    //
        orderBooks[orderId] = {                                     // Add the attribute orderId to the order book array and initialize it with the constructed object.
            Type: ORDER_TYPE_BUY,                                   // Constructed Object Type Property: Type buy
            Status: ORDER_STATE_PENDING,                            //                       State     on hold
            Id: 0,                                                  //                       orderID    0
            Price: price,                                           //                       price   paramter  price
            Amount: amount,                                         //                       Order quantity parameter amount 
            Extra: extra                                            //                       Extended information A string processed by valuesToString
        };
        orderBooksLen++;                                            // The length of the order book is increased by 1
        return orderId;                                             // Returns the orderId of the order constructed this time (non-exchange order ID, don't confuse.)
    };
    this.Sell = function(price, amount, extra) {                    // Basically similar to this.Buy, construct a sell order.
        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() {                                   // Get unfinished order information
        var orders = _C(exchange.GetOrders);                        // Call API GetOrders to get unfinished order information Assigned to orders
        for (orderId in orderBooks) {                               // Traversing the orderBooks in the Trader object 
            var order = orderBooks[orderId];                        // Take out the order based on orderId
            if (order.Status !== ORDER_STATE_PENDING) {             // If the state of order is not equal to the suspended state, skip this loop
                continue;
            }
            var found = false;                                      // Initialize the found variable (marked if found) to true
            for (var i = 0; i < orders.length; i++) {               // Traversing data for unexecuted orders returned by the API  
                if (orders[i].Id == order.Id) {                     // When you find an order with the same order id in orderBooks, assign a value of true to find, which means find. 
                    found = true;                                   
                    break;                                          // Jump out of the current loop
                }
            }
            if (!found) {                                           // If not found, push orderBooks[orderId] to orders.
                orders.push(orderBooks[orderId]);                   // Why do you want to push like this?
            }
        }
        return orders;                                              // return orders
    }
    this.GetOrder = function(orderId) {                             // Get order
        if (typeof(orderId) === 'number') {                         // If the passed argument orderId is a numeric type 
            return exchange.GetOrder(orderId);                      // Call the API GetOrder to get the order information based on the orderId and return.
        }
        if (typeof(hisBooks[orderId]) !== 'undefined') {            // Typeof(hisBooks[orderId]) if not equal to undefined
            return hisBooks[orderId];                               // Return data in hisBooks with attribute as orderId
        }
        if (typeof(orderBooks[orderId]) !== 'undefined') {          // As above, if there is a value of orderId in the orderBooks, this data is returned.
            return orderBooks[orderId];
        }
        return null;                                                // Return null if the above conditions are not met
    };
    this.Len = function() {                                         // Returns the Trader's orderBookLen variable, which returns the order book length.
        return orderBooksLen;
    };
    this.RealLen = function() {                                     // Back In the order book Activate the order quantity.
        var n = 0;                                                  // Initial count is 0
        for (orderId in orderBooks) {                               // Traversing the order book
            if (orderBooks[orderId].Id > 0) {                       // If the Id of the current order in the traversal is greater than 0, that is, 0 other than the initial time, 
                                                                    // indicating that the order has been placed, the order has been activated.
                n++;                                                // Cumulatively activated order
            }
        }
        return n;                                                   // Returns the value of n, which returns the true order book length. (number of orders activated)
    };
    this.Poll = function(ticker, priceDiff) {                       // 
        var orders = _C(exchange.GetOrders);                        // Get all unexecuted orders
        for (orderId in orderBooks) {                               // Traversing the order book
            var order = orderBooks[orderId];                        // Take the current order assign to order
            if (order.Id > 0) {                                     // If the order is active, ie order.Id is not 0 (already placed)
                var found = false;                                  // Variable found (mark found) is false
                for (var i = 0; i < orders.length; i++) {           // Find the same order number in the executed order information returned by the exchange
                    if (order.Id == orders[i].Id) {                 // If found, assign a value of true to find, which means it has been found.
                        found = true;
                    }
                }
                if (!found) {                                       // If the current orderId represents an order that is not found in the order of the uncompleted order returned by the exchange.
                    order.Status = ORDER_STATE_CLOSED;              // Updates the order corresponding to orderId in orderBooks (ie the current order variable) and updates
                                                                    // the Status property to ORDER_STATE_CLOSED (ie closed) 
                    hisBooks[orderId] = order;                      // The completed order is recorded in the historical order book, ie hisBooks, unified, and the unique order number orderId
                    delete(orderBooks[orderId]);                    // Delete the attribute of the order book named orderId value. (The completed order is deleted from it)
                    orderBooksLen--;                                // Order book length reduction
                    continue;                                       // The following code skips the loop.
                }
            }
            var diff = _N(order.Type == ORDER_TYPE_BUY ? (ticker.Buy - order.Price) : (order.Price - ticker.Sell));
            // Diff is the difference between the planned opening price of the order in the current order book and the current real-time opening price.

            var pfn = order.Type == ORDER_TYPE_BUY ? exchange.Buy : exchange.Sell;   // Assign the corresponding API function reference to pfn according to the type of the order.
            // That is, if the order type is a buy order, pfn is a reference to the exchange.Buy function, the same as the sell order.

            if (order.Id == 0 && diff <= priceDiff) {                                // If the order order in the order book is not activated (ie Id is equal to 0) and the current price is less than or 
                                                                                     // equal to the order plan price, the priceDiff passed in the parameter.   
                var realId = pfn(order.Price, order.Amount, order.Extra + "(distance: " + diff + (order.Type == ORDER_TYPE_BUY ? (" ask price: " + ticker.Buy) : (" bid price: " + ticker.Sell))+")");
                // Execute order function, parameter passing price, quantity, order extension information + pending order distance + market data (ask price or bid price), return exchange order id

                if (typeof(realId) === 'number') {    // If the returned realId is a numeric type
                    order.Id = realId;                // Assign the Id attribute of the current order order to the order book.
                }
            } else if (order.Id > 0 && diff > (priceDiff + 1)) {  // If the order is active and the current distance is greater than the distance passed in by the parameter
                var ok = true;                                    // Declare a variable for tagging       Initially set true 
                do {                                              // Execute "do" first and then judge while    
                    ok = true;                                    // Ok assign true
                    exchange.CancelOrder(order.Id, "unnecessary" + (order.Type == ORDER_TYPE_BUY ? "buying" : "selling"), "placed order price:", order.Price, "volume:", order.Amount, ", distance:", 
                                                 diff, order.Type == ORDER_TYPE_BUY ? ("ask price: " + ticker.Buy) : ("bid price: " + ticker.Sell));
                    // Cancel the pending order that is out of range. After canceling the order, print the current order information and the current distance diff.

                    Sleep(200);                                   // Wait 200 milliseconds
                    orders = _C(exchange.GetOrders);              // Call the API to get an uncompleted order in the exchange.
                    for (var i = 0; i < orders.length; i++) {     // Traverse these unfinished orders.
                        if (orders[i].Id == order.Id) {           // If the cancelled order is found in the list of orders that have not been completed by the exchange
                            ok = false;                           // Assign ok this variable to false, that is, no cancellation is successful.
                        }
                    }
                } while (!ok);                                    // If ok is false, then !ok is true and while will continue to repeat the loop, continue to cancel the order,
                                                                  // and check if the cancellation is successful.
                order.Id = 0;                                     // Assigning a value of 0 to order.Id means that the current order is inactive.
            }
        }
    };
}

function balanceAccount(orgAccount, initAccount) {               // Balance Account Function Parameter Initial account information when the strategy is started
    cancelPending();                                             // Call the custom function cancelPending() to cancel all pending orders.
    var nowAccount = _C(exchange.GetAccount);                    // Declare a variable nowAccount to record the latest information about the account at the moment.
    var slidePrice = 0.2;                                        // Set the slip price when placing the order as 0.2
    var ok = true;                                               // Tag variable initially set true
    while (true) {                                               // while loop
        var diff = _N(nowAccount.Stocks - initAccount.Stocks);   // Calculate the difference between the current account and the initial account diff
        if (Math.abs(diff) < exchange.GetMinStock()) {           // If the absolute value of the currency difference is less than the minimum transaction volume of the exchange,
                                                                 // the break jumps out of the loop and does not perform balancing operations.
            break;
        }
        var depth = _C(exchange.GetDepth);                       // Get the exchange depth information Assign to the declared depth variable 
        var books = diff > 0 ? depth.Bids : depth.Asks;          // According to the difference of the currency is greater than 0 or less than 0, extract the buy order array or 
                                                                 // sell order array in depth (equal to 0 will not be processed, it is break when it is judged to be less than GetMinStock)
                                                                 // The difference between the coins is greater than 0 to sell the balance, so look at the buy order array, 
                                                                 // the difference between the coins is less than 0 is the opposite.
        var n = 0;                                               // Statement n initial is 0
        var price = 0;                                           // Statement price initial 0
        for (var i = 0; i < books.length; i++) {                 // Traversing the buy or sell order array
            n += books[i].Amount;                                // Accumulate Amount (order quantity) for each order based on the index i traversed
            if (n >= Math.abs(diff)) {                           // If the cumulative order quantity n is greater than or equal to the currency difference, then:
                price = books[i].Price;                          // Get the price of the current indexed order, assign it to price
                break;                                           // Jump out of the current for traversal cycle
            }
        }
        var pfn = diff > 0 ? exchange.Sell : exchange.Buy;       // Pass the sell order API (exchange.Sell) or the next buy order API (exchange.Buy) reference to the declared pfn
                                                                 // based on the currency difference greater than 0 or less than 0
        var amount = Math.abs(diff);                             // The amount of the order to be balanced is diff, the difference in the currency, assigned to the declared amount variable.
        var price = diff > 0 ? (price - slidePrice) : (price + slidePrice);    // The direction of buying and selling according to the difference in the currency, increase or decrease the
                                                                               // slip price based on the price (slip price is to make it easier to trade), and then assign it to price
        Log("start the balance", (diff > 0 ? "sell" : "buy"), amount, "of coins");           // The number of coins that the output log balances.
        if (diff > 0) {                                                        // According to the direction of the buying and selling, determine whether the account currency or the amount of coins is sufficient.
            amount = Math.min(nowAccount.Stocks, amount);                      // Make sure that the order amount will not exceed the available coins of the current account.
        } else {
            amount = Math.min(nowAccount.Balance / price, amount);             // Make sure that the amount of order placed does not exceed the amount of money available in the current account.
        }
        if (amount < exchange.GetMinStock()) {                                 // Check if the final order quantity is less than the minimum order quantity allowed by the exchange
            Log("Insufficient funds, unable to balance to the initial state");                  // If the order quantity is too small, the information is printed.
            ok = false;                                                        // Tag balance failed
            break;                                                             // Jump out of the while loop
        }
        pfn(price, amount);                                                     // Execute order API (pfn reference)
        Sleep(1000);                                                            // Pause for 1 second
        cancelPending();                                                        // Cancel all pending orders.
        nowAccount = _C(exchange.GetAccount);                                   // Get current account information
    }
    if (ok) {                                                                   // Execute the code inside curly braces when ok is true (balance is successful)
        LogProfit(_N(nowAccount.Balance - orgAccount.Balance));                 // Use the Balance property of the incoming parameter orgAccount (account information before balancing) 
                                                                                // to subtract the Balance property of the current account information, that is, the difference in the amount of money. 
                                                                                // That is, profit and loss (because the number of coins does not change, there is a slight error because some small
                                                                                // amounts cannot be balanced)
        Log("平衡完成", nowAccount);                                             // The output log is balanced.
    }
}

var STATE_WAIT_OPEN = 0;                                                        // Used for the state of each node in the fishTable
var STATE_WAIT_COVER = 1;                                                       // ...
var STATE_WAIT_CLOSE = 2;                                                       // ...
var ProfitCount = 0;                                                            // Profit and loss record
var BuyFirst = true;                                                            // Initial interface parameters
var IsSupportGetOrder = true;                                                   // determine the exchange support the GetOrder API function, a global variable, used to determine the start of the main function
var LastBusy = 0;                                                               // Record the last processed time object

function setBusy() {                            // Set Busy time
    LastBusy = new Date();                      // Assign LastBusy to the current time object
}

function isTimeout() {                                                          // Determine if it times out
    if (MaxIdle <= 0) {                                                         // Maximum idle time (based on whether the grid is automatically moved), 
                                                                                // if the maximum idle time MaxIdle is set less than or equal to 0
        return false;                                                           // Returns false, does not judge the timeout. That is, always return false without timeout.
    }
    var now = new Date();                                                       // Get current time object
    if (((now.getTime() - LastBusy.getTime()) / 1000) >= MaxIdle) {             // Use the getTime function of the current time object to get the timestamp and the timestamp of LastBusy to calculate the difference,
                                                                                // Divide by 1000 to calculate the number of seconds between the two time objects. 
                                                                                // Determine if it is greater than the maximum idle time MaxIdle
        LastBusy = now;                                                         // If it is greater than, update LastBusy to the current time object now
        return true;                                                            // Returns true, which is a timeout.
    }
    return false;                                                               // Return false no timeout
}

function onexit() {                             // The closing function when the program exits.
    if (CancelAllWS) {                          // To cancel all pending orders when stop, call cancelPending() to cancel all pending orders.
        Log("Exiting, try to cancel all pending orders");
        cancelPending();
    }
    Log("Strategy successfully stopped");
    Log(_C(exchange.GetAccount));               // Print the account position information when you exit the program.
}


function fishing(orgAccount, fishCount) {    // Casting parameters: account information, number of casting
    setBusy();                               // Set LastBuys to the current timestamp
    var account = _C(exchange.GetAccount);   // Declare an account variable to get the current account information and assign it.
    Log(account);                            // Output the account information at the start of the call to the fishing function.
    var InitAccount = account;               // Declare a variable InitAccount and assign it with account. Here is the initial account funds recorded before 
                                             // this casting, used to calculate floating profit and loss.   
    var ticker = _C(exchange.GetTicker);     // Get the quote value assigned to the declared ticker variable 
    var amount = _N(AmountOnce);             // According to the number of interface parameters, use _N to process the decimal places (_N defaults to 2 bits) and assign them to amount.
    var amountB = [amount];                  // Declare a variable called amountB is an array, initialize an element with amount
    var amountS = [amount];                  // Declare a variable called amountS ...
    if (typeof(AmountType) !== 'undefined' && AmountType == 1) {     // According to the custom amount, the order size type, if this interface parameter is not undefined,
                                                                     // And AmountType is set to a custom amount on the interface, that is, the AmountType value is 1 (the index of the drop-down box) 
        for (var idx = 0; idx < AllNum; idx++) {      // The total number of AllNum. If you set a custom amount, cycle amountB/amountS to the order quantity array according to the total number of cycles.
            amountB[idx] = BAmountOnce;               // Assign value to the buy order array using interface parameters
            amountS[idx] = SAmountOnce;               // ...          to the sell order...
        }
    } else {                                          // else
        for (var idx = 1; idx < AllNum; idx++) {      // Cycles based on the total number of grids.
            switch (AmountCoefficient[0]) {           // According to the interface parameter difference, the first character of the string, AmountCoefficient[0] is '+', '-', '*', '/'
                case '+':                             // According to the interface parameters, a grid with a single addition and increment is constructed.
                    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);   // buying order, buying amount, and process the data decimal places.
            amountS[idx] = amountB[idx];                  // Assignment
        }
    }
    if (FirstPriceAuto) {                                 // If the first parameter is automatically set to true if the interface parameter is set, the code inside the if curly brackets is executed.
        FirstPrice = BuyFirst ? _N(ticker.Buy - PriceGrid, Precision) : _N(ticker.Sell + PriceGrid, Precision);
        // The interface parameter FirstPrice sets the first price according to the BuyFirst global variable (the initial statement is true, 
        // and has been assigned according to the OpType at the beginning of the main). 
        // The price is set by the price ticker and the price parameter PriceGrid price interval.  
    }
    // Initialize fish table   
    var fishTable = {};                         // Declare a grid object
    var uuidTable = {};                         // Identification code table object
    var needStocks = 0;                         // Required coins variable
    var needMoney = 0;                          // Required money variable
    var actualNeedMoney = 0;                    // Actually needed money
    var actualNeedStocks = 0;                   // Actually needed coins
    var notEnough = false;                      // Underfunded tag variable, initially set to false
    var canNum = 0;                             // Available grid
    for (var idx = 0; idx < AllNum; idx++) {    // The structure is traversed according to the number of grid AllNum.
        var price = _N((BuyFirst ? FirstPrice - (idx * PriceGrid) : FirstPrice + (idx * PriceGrid)), Precision);
        // When traversing the construct, the current index idx price setting is set according to BuyFirst. The spacing between each index price is PriceGrid.
        needStocks += amountS[idx];                      // The number of coins sold is gradually accumulated with the cycle. (accumulated by the sell order quantity array to needStocks one by one)
        needMoney += price * amountB[idx];               // The amount of money required to buy is gradually accumulated with the cycle. (....buy the order quantity array one by one...)
        if (BuyFirst) {                                  // Handling buy first 
            if (_N(needMoney) <= _N(account.Balance)) {  // If the grid requires less money than the amount of money available on the account
                actualNeedMondy = needMoney;             // Assigned to the actual amount of money required
                actualNeedStocks = needStocks;           // Assigning to the actual number of coins required. Is there something wrong with this?
                canNum++;                                // Cumulative number of available grids
            } else {                                     // _N(needMoney) <= _N(account.Balance) If this condition is not met, set the underfunded tag variable to true
                notEnough = true;
            }
        } else {                                         // Handling sell first
            if (_N(needStocks) <= _N(account.Stocks)) {  // Check if the required number of coins is less than the number of coins available in the account
                actualNeedMondy = needMoney;             // Assignment
                actualNeedStocks = needStocks;
                canNum++;                                // Cumulative number of available grids
            } else {
                notEnough = true;                        // Set true if the funding conditions are not met
            }
        }
        fishTable[idx] = STATE_WAIT_OPEN;                // According to the current index idx, set the state of the idx member (grid node) of the grid object,
                                                         // initially STATE_WAIT_OPEN (waiting to open the position)
        uuidTable[idx] = -1;                             // The numbered object also initializes its own idx value (the node corresponding to the fishTable) to -1 based on the current idx.
    }
    if (!EnableAccountCheck && (canNum < AllNum)) {      // If the funds check is not enabled, and the number of grids (the total number of nodes) where the node is smaller 
                                                         // than the interface parameter setting can be opened.
        Log("Warning, current funds can only be made", canNum, "of Grids, total grid needs", (BuyFirst ? needMoney : needStocks), "Please keep sufficient funds");   // Log outputs a warning message.
        canNum = AllNum;                                                                                          // Update the number of openable settings for the interface parameters
    }
    if (BuyFirst) {                                                                         // buy first
        if (EnableProtectDiff && (FirstPrice - ticker.Sell) > ProtectDiff) {                // Open spread protection and enter the market price minus the current bid price more than
                                                                                            // the market entry price protection         
            throw "The first buying price is higher than the market selling price" + _N(FirstPrice - ticker.Sell, Precision) + ' dollar';  // Throw an error message.
        } else if (EnableAccountCheck && account.Balance < _N(needMoney)) {                 // If the funds check is enabled and the amount of money available for the account is less than
                                                                                            // the amount of money required for the grid.
            if (fishCount == 1) {                                                           // If it is the first time to cast the grid
                throw "Insufficient funds, need" + _N(needMoney) + "dollar";                // Throw an error, insufficient funds
            } else {
                Log("Insufficient funds, need", _N(needMoney), "dollar, the program only make", canNum, "of grids #ff0000");  // If it is not the first time to cast a grid, output a message.
            }
        } else {                                                                            // In other cases, there is no capital inspection, price protection, etc.
            Log('Estimated use of funds: ', _N(needMoney), "dollar");                            // The output is expected to use funds.
        }
    } else {                                                                                // sell first, The following is similar to "buy first"
        if (EnableProtectDiff && (ticker.Buy - FirstPrice) > ProtectDiff) {
            throw "The first selling price is higher than the market buying price" + _N(ticker.Buy - FirstPrice, Precision) + ' dollar';
        } else if (EnableAccountCheck && account.Stocks < _N(needStocks)) {
            if (fishCount == 1) {
                throw "Insufficient funds, need" + _N(needStocks) + " of coins";
            } else {
                Log("Insufficient funds, need", _N(needStocks), "of coins, program only make", canNum, "of grids #ff0000");
            }
        } else {
            Log('Estimated use of funds: ', _N(needStocks), "coins, approximately", _N(needMoney), "dollar");
        }
    }

    var trader = new Trader();                                          // Constructs a Trader object, assigning it to the trader variable declared here.
    var OpenFunc = BuyFirst ? exchange.Buy : exchange.Sell;             // According to whether to buy and sell first, set the open function OpenFunc to refer to exchange.Buy or exchange.Sell
    var CoverFunc = BuyFirst ? exchange.Sell : exchange.Buy;            // same as above
    if (EnableDynamic) {                                                // Set OpenFunc/CoverFunc again according to whether the interface parameter EnableDynamic is enabled.
        OpenFunc = BuyFirst ? trader.Buy : trader.Sell;                 // The member function Buy that references the trader object is used for dynamic pending orders (mainly because 
                                                                        // some exchanges limit the number of pending orders, so virtual dynamic pending orders are required)
        CoverFunc = BuyFirst ? trader.Sell : trader.Buy;                // same as above
    }
    var ts = new Date();                                                // Create a time object at this time (assigned to ts) to record the time at the moment.
    var preMsg = "";                                                    // Declare a variable to record the last message, the initial set to empty string
    var profitMax = 0;                                                  // Maximum return 
    while (true) {                                                      // The main logic after the grid is casted
        var now = new Date();                                           // Record the time when the current cycle started
        var table = null;                                               // Declare a variable
        if (now.getTime() - ts.getTime() > 5000) {                      // Calculate whether the difference between the current time now and the recorded time ts is greater than 5000 milliseconds
            if (typeof(GetCommand) == 'function' && GetCommand() == "Receiving grid") {         // Check if the strategy interaction control command "receives the grid" is received, 
                                                                                                // stops and balances to the initial state.
                Log("Start executing commands to perform grid operations");                                          // Output information 
                balanceAccount(orgAccount, InitAccount);                              // Perform a balancing function to balance the number of coins to the initial state
                return false;                                                         // This time the grid function is fishing and return false
            }
            ts = now;                                                                 // Update ts with current time now for next comparison time
            var nowAccount = _C(exchange.GetAccount);                                 // Declare the nowAccount variable and initially been set as the current account information. 
            var ticker = _C(exchange.GetTicker);                                      // Declare the ticker variable and initially been set as the current market information.
            if (EnableDynamic) {                                                      // If you enable dynamic pending orders
                trader.Poll(ticker, DynamicMax);                                      // Call the Poll function of the trader object to detect and process all orders based on the 
                                                                                      // current ticker market and the interface parameter DynamicMax.
            }
            var amount_diff = (nowAccount.Stocks + nowAccount.FrozenStocks) - (InitAccount.Stocks + InitAccount.FrozenStocks);  // Calculate the current coin difference
            var money_diff = (nowAccount.Balance + nowAccount.FrozenBalance) - (InitAccount.Balance + InitAccount.FrozenBalance); // Calculate the current money difference
            var floatProfit = _N(money_diff + (amount_diff * ticker.Last));           // Calculate the current floating profit and loss of this time of casting grid
            var floatProfitAll = _N((nowAccount.Balance + nowAccount.FrozenBalance - orgAccount.Balance - orgAccount.FrozenBalance) + ((nowAccount.Stocks + nowAccount.FrozenStocks 
                                     - orgAccount.Stocks - orgAccount.FrozenStocks) * ticker.Last));
            // Calculate the overall floating profit and loss

            var isHold = Math.abs(amount_diff) >= exchange.GetMinStock();             // If the absolute value of the coin difference at this moment is greater than the minimum trading 
                                                                                      // volume of the exchange, it means that the position has been held.
            if (isHold) {                                                             // If you have already held a position, execute the setBusy() function, which will update the LastBusy time.
                setBusy();                                                            // That is, after opening the position, the opening of the opening mechanism is started.
            }

            profitMax = Math.max(floatProfit, profitMax);                             // Refresh the maximum floating profit and loss
            if (EnableAccountCheck && EnableStopLoss) {                               // If you initiate account detection and start a stop loss
                if ((profitMax - floatProfit) >= StopLoss) {                          // If the maximum floating profit or loss minus the current floating profit or loss is greater than or equal to 
                                                                                      // the maximum floating loss value, execute the code inside the curly braces
                    Log("Current floating profit a

More