Profit Harvester Strategy Analysis (2)

Author: Ninabadass, Created: 2022-04-26 15:57:02, Updated: 2022-04-26 15:57:53

Profit Harvester Strategy Analysis (2)

Let’s continue the content of last time to explain.

Third Added Function:

    self.balanceAccount = function() {
        var account = exchange.GetAccount()
        if (!account) {
            return
        }
        self.account = account
        var now = new Date().getTime()
        if (self.orderBook.Bids.length > 0 && now - self.preCalc > (CalcNetInterval * 1000)) {
            self.preCalc = now
            var net = _N(account.Balance + account.FrozenBalance + self.orderBook.Bids[0].Price * (account.Stocks + account.FrozenStocks))
            if (net != self.preNet) {
                self.preNet = net
                LogProfit(net)
            }
        }
        self.btc = account.Stocks
        self.cny = account.Balance
        self.p = self.btc * self.prices[self.prices.length-1] / (self.btc * self.prices[self.prices.length-1] + self.cny)
        var balanced = false
        
        if (self.p < 0.48) {
            Log("start to balance", self.p)
            self.cny -= 300
            if (self.orderBook.Bids.length >0) {
                exchange.Buy(self.orderBook.Bids[0].Price + 0.00, 0.01)
                exchange.Buy(self.orderBook.Bids[0].Price + 0.01, 0.01)
                exchange.Buy(self.orderBook.Bids[0].Price + 0.02, 0.01)
            }
        } else if (self.p > 0.52) {
            Log("start to balance", self.p)
            self.btc -= 0.03
            if (self.orderBook.Asks.length >0) {
                exchange.Sell(self.orderBook.Asks[0].Price - 0.00, 0.01)
                exchange.Sell(self.orderBook.Asks[0].Price - 0.01, 0.01)
                exchange.Sell(self.orderBook.Asks[0].Price - 0.02, 0.01)
            }
        }
        Sleep(BalanceTimeout)
        var orders = exchange.GetOrders()
        if (orders) {
            for (var i = 0; i < orders.length; i++) {
                if (orders[i].Id != self.tradeOrderId) {
                    exchange.CancelOrder(orders[i].Id)
                }
            }
        }
    }

When the constructor LeeksReaper() is constructing an object, the balanceAccount() function added to the object is used to update the account asset information, which is stored in self.account, that is, to construct The attribute account of the object. Calculate and print the return value regularly. Then, according to the latest account asset information, the balance ratio of spot currency symbols (spot position balance) is calculated, and when the offset threshold is triggered, small orders are closed to make the symbols (positions) back to a balanced state. Wait for a certain period of time to execute trading, and then cancel all pending orders, and execute the function in the next round, the balance will be detected again and the corresponding processing will be made.

Let’s look at the code of this function statement by statement: First of all, the first statement var account = exchange.GetAccount() declares a local variable account, calls the exchange.GetAccount() function in FMZ API interface, get the latest data of the current account and assign it to the variableaccount. Then, judge the variable account; if the variable value is null (which will happen when it fails to obtain the variable, such as timeout, network, platform interface exception, etc.), it will return directly (corresponding to if (!account ){...} here).

The statement self.account = account is to assign the local variable account to the attribute account of the constructed object to record the latest account information in the constructed object.

The statement var now = new Date().getTime() declares a local variable now, and calls the getTime() function of the time & date object of the JavaScript language to return the current timestamp, and assign the timestamp to the variable now.

The code: if (self.orderBook.Bids.length > 0 && now - self.preCalc > (CalcNetInterval * 1000)) {...} judges the difference between the current timestamp and the last recorded timestamp; if the value exceeds the parameter CalcNetInterval * 1000, it means that it has exceeded CalcNetInterval * 1000 milliseconds (CalcNetInterval seconds) from the last update to the present, which realizes the function of printing the return regularly. Since the buy 1 price in the market needs to be used when calculating the profit, the condition is also limited to the condition self.orderBook.Bids.length > 0 (depth data, which must be valid in the buy order list as the level information).

When the condition of the statement “if” is triggered, execute self.preCalc = now to update the timestamp variable self.preCalc of the latest printed profit to the current timestamp now. Here, the profit statistics use the net value calculation method, the code is: var net = _N(account.Balance + account.FrozenBalance + self.orderBook.Bids[0].Price * (account.Stocks + account.FrozenStocks)), that is, converting the currency into asset (quote currency) according to the current buy 1 price, and then add it together with the asset amount in the account and assign it to the declared local variable net. Determine whether the current total net value is consistent with the last recorded total net value:

            if (net != self.preNet) {
                self.preNet = net
                LogProfit(net)
            }

If inconsistent, i.e. net != self.preNet is true, update the attribute self.preNet that records the net value with the net variable. Then, print the total net value data net to the profit curve chart of the FMZ Quant Trading Platform bot (you can query the LogProfit function in the FMZ API documentation).

If the regular printing of return is not triggered, then continue the following process: record account.Stocks (the current available currency symbols in the account) and account.Balance (the current available assets in the account) in self.btc and self.cny. Calculate the offset ratio and assign it, which is recorded in self.p.

self.p = self.btc * self.prices[self.prices.length-1] / (self.btc * self.prices[self.prices.length-1] + self.cny)

The algorithm is also very simple, which is to calculate how many percentage of the current currency value in the total net value of the account.

So how do you judge when the currency (position) balance is triggered? Here, the developer uses 50% up and down 2 percentage points as a buffer; if it exceeds the buffer, execute the balance, that is, when self.p < 0.48, the currency balance offset is triggered. If you think the currency amount is little, each time the price increases by 0.01, place three small orders. Similarly, if the currency balance is self.p > 0.52, if you think the currency amount is large, pend small orders of sell 1 price in the market. Finally, wait for a certain period of time, according to the parameter settings Sleep(BalanceTimeout), and cancel all orders.

        var orders = exchange.GetOrders()                  # obtain all the current pending orders, and save them in the variable orders"
        if (orders) {                                      # if the variable "orders", which obtains all the current pending orders, is not null
            for (var i = 0; i < orders.length; i++) {      # use the loop to traverse "orders", and cancel the orders one by one 
                if (orders[i].Id != self.tradeOrderId) {
                    exchange.CancelOrder(orders[i].Id)     # call "exchange.CancelOrder", and cancel orders by "orders[i].Id"
                }
            }
        }

Forth Added Function:

Here comes the core part of the strategy, the highlight. The self.poll = function() {...} function is the main logic of the entire strategy. We also talked about it in the previous article. In the main( ) function, start to execute; before entering the while infinite loop, we use var reaper = LeeksReaper() to construct the profit harvester object, and then Reaper.poll() is called cyclically in the main() function.

The self.poll function starts to execute, and does some preparations before each loop; self.numTick++ increments the count; self.updateTrades() updates the recent trading records in the market and calculates the related data used; self.updateOrderBook() updates the market (order book) data and calculates the relevant data; self.balanceAccount() checks the currency (position) balance.

        var burstPrice = self.prices[self.prices.length-1] * BurstThresholdPct   # calculate the burst price 
        var bull = false             # declare the variable marked by the bull market; the initial value is false
        var bear = false             # declare the variable marked by the bear market; the initial value is false
        var tradeAmount = 0          # declare the variable of trading amount; the initial value is 0

Next, we need to judge if the current short-term market is a bull or a bear.

        if (self.numTick > 2 && (
            self.prices[self.prices.length-1] - _.max(self.prices.slice(-6, -1)) > burstPrice ||
            self.prices[self.prices.length-1] - _.max(self.prices.slice(-6, -2)) > burstPrice && self.prices[self.prices.length-1] > self.prices[self.prices.length-2]
            )) {
            bull = true
            tradeAmount = self.cny / self.bidPrice * 0.99
        } else if (self.numTick > 2 && (
            self.prices[self.prices.length-1] - _.min(self.prices.slice(-6, -1)) < -burstPrice ||
            self.prices[self.prices.length-1] - _.min(self.prices.slice(-6, -2)) < -burstPrice && self.prices[self.prices.length-1] < self.prices[self.prices.length-2]
            )) {
            bear = true
            tradeAmount = self.btc
        }

Do you remember the self.updateOrderBook() function in the previous article, in which we used the weighted average algorithm to construct a time series prices array in order? This piece of code uses three new functions, namely _.min, _.max, slice, which are also very easy to understand.

  • _.min: The function is to find the minimum in the parameter array.

  • _.max: The function is to find the maximum in the parameter array.

  • slice: This function is a member function of the JavaScript array object. It is to intercept and return a part of the array according to the index. For example:

    function main() {
        // index     .. -8 -7 -6 -5 -4 -3 -2 -1
        var arr = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
        Log(arr.slice(-5, -1))    // it will intercept several elements from 4 to 1, and return a new array: [4,3,2,1]
    }
    

Here, the conditions to judging whether it is a bull market or a bear market are:

  • self.numTick > 2 must be true, that is, if the price burst happens in a new round of detection, it has to be triggered after at least three rounds of detection, and avoid trigger at the beginning.
  • The last data in the price series self.prices, that is, the difference between the latest data and the maximum or minimum price in the self.prices array in the previous range should break through the burstPrice .

If all conditions are true, mark bull or bear as true, and assign a value to the variable tradeAmount, and plan a stud trading.

Then, for the parameter BurstThresholdVol, based on the self.vol updated and calculated in the previous self.updateTrades() function, it is decided whether to reduce the trading intensity (to reduce the planned trading volume).

        if (self.vol < BurstThresholdVol) {
            tradeAmount *= self.vol / BurstThresholdVol   // reduce the planned trading volume, and reduce it to the previous volume multiplied by "self.vol / BurstThresholdVol" 
        }
        
        if (self.numTick < 5) {
            tradeAmount *= 0.8      // reduced to 80% of the plan 
        }
        
        if (self.numTick < 10) {    // reduced to 80% of the plan
            tradeAmount *= 0.8
        }

Next, judge whether the trading signal and trading volume meet the requirements:

        if ((!bull && !bear) || tradeAmount < MinStock) {   # if it is not a bull market nor a bear market, or the planned trading volume "tradeAmount" is less than the minimum trading volume "MinStock" set by the parameter, the "poll" function returns directly without any trading operation
            return
        }

After the above judgment, execute var tradePrice = bull ? self.bidPrice : self.askPrice. According to whether it is a bear market or a bull market, set the trading price and assign tthe value with the corresponding delivery order price.

Finally, enter a while loop; the only stop and breakout condition of the loop is tradeAmount >= MinStock, that is, the planned trading volume is less than the minimum trading volume. In the loop, according to the current bull market state or the bear market state, execute ordering. And record the order ID in the variable orderId. Execute Sleep(200) to wait for 200 milliseconds after placing an order in each round. The loop then judges whether orderId is true (if the order fails, the order ID will not be returned, and the “if” condition will not be triggered). If the condition is true, get the order ID and assign it to self.tradeOrderId.

Declare a variable order to store the order data with the initial value of null. Then, use a loop to obtain the order data with the ID, and determine whether the order is in the pending order status; if it is in the pending order status, cancel the order with the ID; if it is not in the pending order status, it will break out of the detection loop.

                var order = null           // declare a variable to save the order data 
                while (true) {             // a while loop 
                    order = exchange.GetOrder(orderId)    // call "GetOrder" to query the order data with the ID of  orderId
                    if (order) {                          // if the order data is queried,and the query fails, the order is null, and "if" will not be triggered  
                        if (order.Status == ORDER_STATE_PENDING) {   // judge whether the current order status is pending order
                            exchange.CancelOrder(orderId)            // if the current order status is pending order, cancel the order 
                            Sleep(200)
                        } else {                                     // if not, execute "break" to break out of the while loop 
                            break
                        }
                    }
                }

Then, perform the following process:

                self.tradeOrderId = 0              // reset "self.tradeOrderId"
                tradeAmount -= order.DealAmount    // update "tradeAmount", and subtract the executed amount of the orders in the delivery order 
                tradeAmount *= 0.9                 // reduce the intensity of ordering  
                if (order.Status == ORDER_STATE_CANCELED) {     // if the order is canceled 
                    self.updateOrderBook()                      // update the data, including the order book data
                    while (bull && self.bidPrice - tradePrice > 0.1) {   // in a bull market, if the updated bid price exceeds the current trading price by 0.1, reduce the trading intensity, and slightly adjust the trading price 
                        tradeAmount *= 0.99
                        tradePrice += 0.1
                    }
                    while (bear && self.askPrice - tradePrice < -0.1) {  // in a bear market, if the updated ask price exceeds the current trading price by 0.1, reduce the trading intensity, and slightly adjust the trading price 
                        tradePrice -= 0.1
                    }
                }

When the program flow breaks out of the while (tradeAmount >= MinStock) {...} loop, it means that the execution of the price burst trading process is completed. Execute self.numTick = 0, that is, reset self.numTick to 0.

The last execution of the constructor LeeksReaper() returns the self object, that is, when var reaper = LeeksReaper(), the object is returned to reaper.

So far, we have analyzed how the LeeksReaper() constructor constructs this profit harvester object, the various methods of the object, and the execution process of the main logic functions. After reading the article, I think you will have a clearer understanding of the high-frequency strategy algorithm process.


More