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 and loss", floatProfit, "Highest profit point:", profitMax, "Start stop loss");   // Output information
                    balanceAccount(orgAccount, InitAccount);                          // Balanced account
                    if (StopLossMode == 0) {                                          // According to the stop loss mode processing, if StopLossMode is equal to 0, the program is exited after the stop loss.
                        throw "Stop loss exit";                                       // Throw the error "Stop Loss Exit" strategy to stop.
                    } else {
                        return true;                                                  // Except for the stop loss exit mode, ie: re-cast the grid after the stop loss.
                    }
                }
            }
            if (EnableAccountCheck && EnableStopWin) {                                 // If the detection account is turned on and the take profit is turned on
                if (floatProfit > StopWin) {                                           // If the floating profit and loss is greater than the take profit
                    Log("Current floating profit and loss", floatProfit, "Start taking profit");                       // Output log
                    balanceAccount(orgAccount, InitAccount);                           // Balanced account recovery initial (take profit)
                    if (StopWinMode == 0) {                                            // According to the take profit mode.
                        throw "Take profit exit";                                      // Exit after taking profit
                    } else {
                        return true;                                                   // Return true after take profit, continue to cast the grid
                    }
                }
            }
            var distance = 0;                                                           // Declare a variable to record the distance
            if (EnableAccountCheck && AutoMove) {                                       // If account detection is turned on and the grid moves automatically
                if (BuyFirst) {                                                         // If it is buy first and then sell 
                    distance = ticker.Last - FirstPrice;                                // Assign a value to distance: the current price minus the first price, calculate the distance 
                } else {                                                                // Other situations: sell first and then buy
                    distance = FirstPrice - ticker.Last;                                // Assign a value to distance: the first price minus the current price, calculate the distance
                }
                var refish = false;                                                     // Whether to re-cast the tag variable 
                if (!isHold && isTimeout()) {                                           // If there is no position (isHold is false) and timeout (isTimeout returns true)
                    Log("no position holding for too long, start moving the grid");                                         
                    refish = true;                                                      // Mark re-cast 
                }
                if (distance > MaxDistance) {                                           // If the current distance is greater than the maximum distance set by the interface parameters, the mark is re-cast
                    Log("The price is too much beyond the grid interval, start moving the grid, the current distance: ", _N(distance, Precision), "current price:", ticker.Last);
                    refish = true;
                }
                if (refish) {                                                           // If refish is true, execute the balance function 
                    balanceAccount(orgAccount, InitAccount);
                    return true;                                                        // This cast function returns true
                }
            }
            var holdDirection, holdAmount = "--",                                       // Declare three variables, position direction, number of positions, position price
                holdPrice = "--";
            if (isHold) {                                                               // When holding a position
                if (RestoreProfit && ProfitAsOrg) {                                     // If you start to restore the last profit and the last profit is included in the average price
                    if (BuyFirst) {                                                     // If it is buy first and then sell 
                        money_diff += LastProfit;                                       // Add the last profit to money_diff, that is, the last time the income is converted into the money difference
                                                                                        // (in the case of the first buy, the money difference is negative, that is, the cost), 
                                                                                        // which is equivalent to the opening cost.
                    } else {                                                            // If it is sell first, then buy
                        money_diff -= LastProfit;                                       // The difference between buying and selling is positive, why - ?
                    }
                }
                // Dealing with buying first and then selling
                holdAmount = amount_diff;                                               // The difference in the value of the coins is given to the number of
                                                                                        // positions (the difference in the coins is the position at the moment)
                holdPrice = (-money_diff) / amount_diff;                                // Calculate the average price of the position by dividing the difference between
                                                                                        // the money and the difference in the coins, 
                                                                                        // Note: If money_diff is negative, then amount_diff must be positive, so be sure 
                                                                                        // to add a minus sign before money_diff so that the calculated price is a positive number.
                // Dealing with selling first and then buying
                if (!BuyFirst) {                                                        // If it is selling first and then buying, it will trigger the update of the open position and 
                                                                                        // the average price of the position.
                    holdAmount = -amount_diff;                                          // The coins difference is negative, so the counter is reversed
                    holdPrice = (money_diff) / -amount_diff;                            // Calculate the average price of the position.
                }
                holdAmount = _N(holdAmount, 4);                                         // Positions, retain 4 decimal places.
                holdPrice = _N(holdPrice, Precision);                                   // The average price of the position, retaining the Precision decimal.
                holdDirection = BuyFirst ? "long" : "short";                            // According to the "first buy then sell" or "first sell then buy" to holdDirection assigned long or short
            } else {                                                                    // If isHold is false, assign "--" to holdDirection
                holdDirection = "--";
            }
            table = {                                                                   // Assign an object to the declared table variable to display the table information on the FMZ robot status bar
                type: 'table',                                                          // See the API documentation LogStatus function, here to initialize the type attribute 'table' for
                                                                                        // display in the status bar as a table
                title: 'Operating status',                                              // Title of the table
                cols: ['Use funds', 'Holding position', 'Position size', 'Average price of position', 'Total floating profit and loss', 'Current grid profit and loss', 
                       'Number of casting grids', 'Grid offset', 'Real placing order', 'Latest coin price'],   // The column name of the table
                rows: [                                                                                                                   // Progressive data for the table
                    [_N(actualNeedMondy, 4), holdDirection, holdAmount, holdPrice, _N(floatProfitAll, 4) + ' ( ' + _N(floatProfitAll * 100 / actualNeedMondy, 4) + ' % )',
                     floatProfit, fishCount, (AutoMove && distance > 0) ? ((BuyFirst ? "up" : "down") + "Deviation: " + _N(distance) + " dollar") : "--", trader.RealLen(), ticker.Last]
                    // One row of data
                ]
            };
            
        }                                                                               // Process some tasks every 5 seconds and update the robot status bar table object table 
        
        var orders = _C(trader.GetOrders);                                              // Get all unexecuted orders
        if (table) {                                                                    // If the table has been assigned a table object
            if (!EnableDynamic) {                                                       // If the dynamic order is not activated
                table.rows[0][8] = orders.length;                                       // Update the length of the pending order array in the first row and the 9th column of the status bar table.
            }
            LogStatus('`' + JSON.stringify(table) + '`');                               // Call the FMZ platform API LogStatus to display the status bar table of the settings
        }
        for (var idx = 0; idx < canNum; idx++) {                                        // Traverse the number of available grids nodes.
            var openPrice = _N((BuyFirst ? FirstPrice - (idx * PriceGrid) : FirstPrice + (idx * PriceGrid)), Precision);        // As the node index idx traverses, construct the opening price
                                                                                                                                // of each node (the direction is buying first, then sold, 
                                                                                                                                // or selling first and then buy) 
            var coverPrice = _N((BuyFirst ? openPrice + PriceDiff : openPrice - PriceDiff), Precision);     // Open and close position spread, that is, the profit margin of each node
            var state = fishTable[idx];                                                                     // Assignment status of the fishnet node
            var fishId = uuidTable[idx];                                                                    // Numbering
            
            // The judgment here is: filtering unfinished orders
            if (hasOrder(orders, fishId)) {                                                                 // If all unexecuted orders, there is an order with the ID fishId in the pending order array
                continue;                                                                                   // Skip this loop and continue the loop
            }
            if (fishId != -1 && IsSupportGetOrder) {                                                        // The grid node id is not equal to the initial value, that is, the order is placed, 
                                                                                                            // and the exchange supports GetOrder
                var order = trader.GetOrder(fishId);                                                        // Get the order for the fishId number
                // The judgment here is as follows: Filter the grid node where the order is not found, the following logic (state == STATE_WAIT_COVER), etc. will not trigger
                if (!order) {                                                                               // Failed to get an order if !order is true 
                    Log("Failed to get order information, ID: ", fishId);                                   // Output log
                    continue;                                                                               // Skip this loop and continue the loop
                }
                // The judgment here is as follows: Filtering the grid nodes that are in a suspended state, not completed, or not fully completed, 
                // the logic of the following judgment (state == STATE_WAIT_COVER) and so on will not be triggered.
                if (order.Status == ORDER_STATE_PENDING) {                                                  // If the order status is pending on the exchange
                    //Log("Order status is not completed, ID: ", fishId);
                    continue;                                                                               // Skip this loop and continue to loop
                }
            }
            if (state == STATE_WAIT_COVER) {                                                                // If the current node status is waiting to be closed
                var coverId = CoverFunc(coverPrice, (BuyFirst ? amountS[idx] : amountB[idx]), (BuyFirst ? 'Complete the buying order:' : 'Complete the selling order:'), openPrice, 'volume:',
                                       (BuyFirst ? amountB[idx] : amountS[idx]));
                // Call the closing function CoverFunc to send the closing order
                if (typeof(coverId) === 'number' || typeof(coverId) === 'string') {        // Determine if the value returned by the closing function is a value (returned directly by the FMZ API) 
                                                                                           // or a string (returned by the buy/Sell function of the trader object)
                    fishTable[idx] = STATE_WAIT_CLOSE;                                     // The closing order has been send and the update status is: STATE_WAIT_CLOSE Waiting for the node task to complete
                    uuidTable[idx] = coverId;                                              // Store the order number in the idx location corresponding to the uuidTable.
                }
            } else if (state == STATE_WAIT_OPEN || state == STATE_WAIT_CLOSE) {            // If the status is waiting for opening or waiting for completion
                var openId = OpenFunc(openPrice, BuyFirst ? amountB[idx] : amountS[idx]);  // Open the position order.
                if (typeof(openId) === 'number' || typeof(openId) === 'string') {          // Determine whether the order placing is successful
                    fishTable[idx] = STATE_WAIT_COVER;                                     // Update status for waiting for closing
                    uuidTable[idx] = openId;                                               // Record current node order ID
                    if (state == STATE_WAIT_CLOSE) {                                       // If it is waiting for completion (it will only be triggered after the open position order is placed)
                        ProfitCount++;                                                     // Cumulative gaining profit times
                        var account = _C(exchange.GetAccount);                             // Get current account information
                        var ticker = _C(exchange.GetTicker);                               // Get current market information
                        var initNet = _N(((InitAccount.Stocks + InitAccount.FrozenStocks) * ticker.Buy) + InitAccount.Balance + InitAccount.FrozenBalance, 8);
                        // Calculate the initial net asset value
                        var nowNet = _N(((account.Stocks + account.FrozenStocks) * ticker.Buy) + account.Balance + account.FrozenBalance, 8);
                        // Calculate the current net asset value
                        var actualProfit = _N(((nowNet - initNet)) * 100 / initNet, 8);    // Calculated rate of return
                        if (AmountType == 0) {                                             // According to the same amount of buy and sell, the custom amount is different.
                            var profit = _N((ProfitCount * amount * PriceDiff) + LastProfit, 8);      // Calculation: the sum of the profit and loss of all profit nodes and the 
                                                                                                      // last time of the net profit and loss which is the total profit and loss
                            Log((BuyFirst ? 'Complete the sell order:' : 'Complete the buy order:'), coverPrice, 'volume:', (BuyFirst ? amountS[idx] : amountB[idx]), 'Closing position profit', profit);
                            // Output order completion information
                        } else {
                            Log((BuyFirst ? 'Complete the sell order:' : 'Complete the buy order:'), coverPrice, 'volume:', (BuyFirst ? amountS[idx] : amountB[idx]));
                        }
                    }
                }
            }
        }
        Sleep(CheckInterval);                        // Grid logic is mainly while loop detection, each time a pause is checked, CheckInterval is: detection interval
    }
    return true;                                     // This time of the casting grid is completed, return true
}
function main() {                                    // The main function of the strategy, the program starts from here.
    if (ResetData) {                                 // RestData is the interface parameter, the default is true, and it controls whether all data is cleared at startup. All are cleared by default.
        LogProfitReset();                            // Execute the API LogProfitReset function to clear all revenue.
        LogReset();                                  // Execute the API LogReset function to clear all logs.
    }
    // exchange.SetMaxDigits(Precision)              // Deprecated, use exchange.SetPrecision instead.
    exchange.SetPrecision(Precision, 3)              // exchange.SetPrecision(2, 3); // Set the price decimal place precision to 2 digits, and the variety order size decimal place precision is 3 digits.
                                                     // Precision Interface parameter。
    if (typeof(AmountType) === 'undefined') {        // Order quantity type, 0: "Buy and sell the same amount", 1: "Custom amount", detect if the parameter is undefined, the default setting is 0.
        AmountType = 0;                              // Typeof will detect the type of AmountType. If undefined is "undefined", assign a value of 0 to AmountType.
    }
    if (typeof(AmountDot) === 'undefined') {         // The maximum number of digits of the order quantity decimal point AmountDot is undefined, set AmountDot to 3.
        AmountDot = 3;                               // In fact, it has been set by exchange.SetPrecision(Precision, 3), and it will be truncated at the bottom.
    }
    if (typeof(EnableDynamic) === 'undefined') {     // Check if the dynamic pending order parameter is enabled. If EnableDynamic is undefined, set to false will not be enabled.
        EnableDynamic = false;
    }
    if (typeof(AmountCoefficient) === 'undefined') { // If not defined, the default setting is "*1"
        AmountCoefficient = "*1";
    }
    if (typeof(EnableAccountCheck) === 'undefined') {// If not defined, the Enable Money Verification parameter is set to true, which is turned on.
        EnableAccountCheck = true;
    }
    BuyFirst = (OpType == 0);                        // Assign value to BuyFirst according to the setting of OpType, OpType sets the grid type, 0: buy first and then sell, 1: sell first and then buy
    IsSupportGetOrder = exchange.GetName().indexOf('itstamp') == -1;    // Check the name of the exchange, whether it is Bitstamp or not
    if (!IsSupportGetOrder) {
        Log(exchange.GetName(), "Does not support GetOrder, may affect the stability of the strategy.");
    }
    SetErrorFilter("502:|503:|S_U_001|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|refused|EOF|When");
    // SetErrorFilter  Filter error messages 
    exchange.SetRate(1); 
    Log('Exchange rate conversion has been disabled, the current currency is', exchange.GetBaseCurrency());    // Disable exchange rate conversion
    if (!RestoreProfit) {     //  If the last profit is restored to false, then LastProfit will be assigned a value of 0, that is, it will not be restored.
        LastProfit = 0;
    }
    var orgAccount = _C(exchange.GetAccount);     // Get account information, here record the initial account information when the strategy starts running, 
                                                  // used to calculate some income, such as: overall floating profit and loss. 
                                                  // There are several parameters in this strategy that are passed to this variable.
    var fishCount = 1;                            // time of casting grids initial is 1
    while (true) {                                // Strategy main loop
        if (!fishing(orgAccount, fishCount)) {    // casting grid function fishing
            break;
        }
        fishCount++;                              // Number of casting grids accumulated
        Log("the number of", fishCount, "casting girds...");      // Output Casting grids information.
        FirstPriceAuto = true;                    // Reset first price is automatically true
        Sleep(1000);                              // Polling interval 1000 milliseconds
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *