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.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.