Baru-baru ini, ada perdebatan hangat tentangprint money
Sebuah strategi yang sangat lama telah masuk ke mata Quants lagi: LeeksReaper.
Prinsip perdagangan robotprint money
Jadi, saya membaca kembali strategi asli dengan hati-hati lagi dan melihat versi transplantasi OKCoin transplantasi leeksreaper di FMZ Quant.
Strategi pemetik bawang putih yang ditransplantasikan berdasarkan platform FMZ Quant dianalisis untuk mengeksplorasi gagasan strategi.
Dalam artikel ini, kita akan menganalisis lebih lanjut dari aspek ide strategi dan niat untuk meminimalkan konten membosankan yang terkait dengan pemrograman.
Kode sumber dari [Transplanting OKCoin LeeksReaper] strategi:
function LeeksReaper() {
var self = {}
self.numTick = 0
self.lastTradeId = 0
self.vol = 0
self.askPrice = 0
self.bidPrice = 0
self.orderBook = {Asks:[], Bids:[]}
self.prices = []
self.tradeOrderId = 0
self.p = 0.5
self.account = null
self.preCalc = 0
self.preNet = 0
self.updateTrades = function() {
var trades = _C(exchange.GetTrades)
if (self.prices.length == 0) {
while (trades.length == 0) {
trades = trades.concat(_C(exchange.GetTrades))
}
for (var i = 0; i < 15; i++) {
self.prices[i] = trades[trades.length - 1].Price
}
}
self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) {
// Huobi not support trade.Id
if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
self.lastTradeId = Math.max(trade.Id == 0 ? trade.Time : trade.Id, self.lastTradeId)
mem += trade.Amount
}
return mem
}, 0)
}
self.updateOrderBook = function() {
var orderBook = _C(exchange.GetDepth)
self.orderBook = orderBook
if (orderBook.Bids.length < 3 || orderBook.Asks.length < 3) {
return
}
self.bidPrice = orderBook.Bids[0].Price * 0.618 + orderBook.Asks[0].Price * 0.382 + 0.01
self.askPrice = orderBook.Bids[0].Price * 0.382 + orderBook.Asks[0].Price * 0.618 - 0.01
self.prices.shift()
self.prices.push(_N((orderBook.Bids[0].Price + orderBook.Asks[0].Price) * 0.35 +
(orderBook.Bids[1].Price + orderBook.Asks[1].Price) * 0.1 +
(orderBook.Bids[2].Price + orderBook.Asks[2].Price) * 0.05))
}
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 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 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)
}
}
}
}
self.poll = function() {
self.numTick++
self.updateTrades()
self.updateOrderBook()
self.balanceAccount()
var burstPrice = self.prices[self.prices.length-1] * BurstThresholdPct
var bull = false
var bear = false
var tradeAmount = 0
if (self.account) {
LogStatus(self.account, 'Tick:', self.numTick, ', lastPrice:', self.prices[self.prices.length-1], ', burstPrice: ', burstPrice)
}
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
}
if (self.vol < BurstThresholdVol) {
tradeAmount *= self.vol / BurstThresholdVol
}
if (self.numTick < 5) {
tradeAmount *= 0.8
}
if (self.numTick < 10) {
tradeAmount *= 0.8
}
if ((!bull && !bear) || tradeAmount < MinStock) {
return
}
var tradePrice = bull ? self.bidPrice : self.askPrice
while (tradeAmount >= MinStock) {
var orderId = bull ? exchange.Buy(self.bidPrice, tradeAmount) : exchange.Sell(self.askPrice, tradeAmount)
Sleep(200)
if (orderId) {
self.tradeOrderId = orderId
var order = null
while (true) {
order = exchange.GetOrder(orderId)
if (order) {
if (order.Status == ORDER_STATE_PENDING) {
exchange.CancelOrder(orderId)
Sleep(200)
} else {
break
}
}
}
self.tradeOrderId = 0
tradeAmount -= order.DealAmount
tradeAmount *= 0.9
if (order.Status == ORDER_STATE_CANCELED) {
self.updateOrderBook()
while (bull && self.bidPrice - tradePrice > 0.1) {
tradeAmount *= 0.99
tradePrice += 0.1
}
while (bear && self.askPrice - tradePrice < -0.1) {
tradeAmount *= 0.99
tradePrice -= 0.1
}
}
}
}
self.numTick = 0
}
return self
}
function main() {
var reaper = LeeksReaper()
while (true) {
reaper.poll()
Sleep(TickInterval)
}
}
Secara umum, ketika Anda mendapatkan strategi untuk dipelajari, Anda harus melihat struktur program secara keseluruhan terlebih dahulu. kode strategi tidak terlalu panjang, dengan kurang dari 200 baris kode, sangat ringkas, dan strategi asli sangat dipulihkan, hampir sama. kode strategi berjalan darimain()
seluruh kode strategi, kecualimain()
, adalah fungsi bernamaLeeksReaper()
.LeeksReaper()
fungsi sangat mudah dipahami, dapat dipahami sebagai konstruktor dari leeksreaper modul logika strategi (objek).LeeksReaper()
bertanggung jawab untuk membangun logika perdagangan leeksheaper.
Kata kunci:
· Jalur pertama dari strategimain
Fungsi:
The next step of strategy ```main``` function:
sementara (benar) { Reaper.poll ((() Tidur ((TickInterval) {\cH00FFFF}
Enter a ```while``` endless loop and keep executing the processing function ```poll()``` of the ```reaper``` object, the ```poll()``` function is exactly where the main logic of the trading strategy lies and the whole strategy program starts executing the trading logic over and over again.
As for the line ```Sleep(TickInterval)```, it is easy to understood, it is to control the pause time after each execution of the overall trading logic, with the purpose of controlling the rotation frequency of the trading logic.
### Analyse ```LeeksReaper()``` constructor
Look at how the ```LeeksReaper()``` function constructs a strategy logic object.
The ```LeeksReaper()``` function starts by declaring an empty object, ```var self = {}```, and during the execution of the ```LeeksReaper()``` function will gradually add some methods and attributes to this empty object, finally completing the construction of this object and returning it (that is, the step of ```main()``` function inside the ```var reaper = LeeksReaper()```, the returned object is assigned to ```reaper```).
Add attributes to the ```self``` object
Next, I added a lot of attributes to ```self```. I will describe each attribute as follows, which can understand the purpose and intention of these attributes and variables quickly, facilitate the understanding of strategies, and avoid being confused when seeing the code.
self.numTick = 0 # It is used to record the number of transactions not triggered when the poll function is called. When the order is triggered and the order logic is executed, self.numTick is reset to 0
self.lastTradeId = 0 # The transaction record ID of the order that has been transacted in the transaction market. This variable records the current transaction record ID of the market
self.vol = 0 # Reference to the trading volume of each market inspection after weighted average calculation (market data is obtained once per loop, which can be interpreted as a time of market inspection)
self.askPrice = 0 # The bill of lading price of the sales order can be understood as the price of the listing order after the strategy is calculated
self.bidPrice = 0 # Purchase order bill of lading price
self.orderBook = {Asks:[], Bids:[]} # Record the currently obtained order book data, that is, depth data (sell one... sell n, buy one... buy n)
self.prices = [] # An array that records the prices on the time series after the calculation of the first three weighted averages in the order book, which means that each time the first three weighted averages of the order book are stored, they are placed in an array and used as a reference for subsequent strategy trading signals, so the variable name is prices, in plural form, indicating a set of prices
self.tradeOrderId = 0 # Record the order ID after the current bill of lading is placed
self.p = 0.5 # Position proportion: when the value of currency accounts for exactly half of the total asset value, the value is 0.5, that is, the equilibrium state
self.account = null # Record the account asset data, which is returned by the GetAccount() function
self.preCalc = 0 # Record the timestamp of the last time when the revenue was calculated, in milliseconds, to control the frequency of triggering the execution of the revenue calculation code
self.preNet = 0 # Record current return values
### Add methods to self objects
After adding these attributes to self, start adding methods to the ```self``` object so that this object can do some work and have some functions.
The first function added:
self.updateTrades = function() {
var trades = _C(exchange.GetTrades) # Call the FMZ encapsulated interface GetTrades to obtain the latest market transaction data
if (self.prices.length == 0) { # When self.prices.length == 0, the self.prices array needs to be filled with numeric values, which will be triggered only when the strategy starts running
while (trades.length == 0) { # If there is no recent transaction record in the market, the while loop will keep executing until the latest transaction data is available and update the trades variable
trades = trades.concat(_C(exchange.GetTrades)) # concat is a method of JS array type, which is used to concatenate two arrays, here is to concatenate the "trades" array and the array data returned by "_C(exchange.GetTrades)" into one array
}
for (var i = 0; i < 15; i++) { # Fill in data to self.prices, and fill in 15 pieces of latest transaction prices
self.prices[i] = trades[trades.length - 1].Price
}
}
self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) { # _. Reduce function is used for iterative calculation to accumulate the amount of the latest transaction records
// Huobi not support trade.Id
if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
self.lastTradeId = Math.max(trade.Id == 0 ? trade.Time : trade.Id, self.lastTradeId)
mem += trade.Amount
}
return mem
}, 0)
}
The function ```updateTrades``` is to get the latest market transaction data and do some calculations based on the data and record it for using in the subsequent logic of the strategy.
The line-by-line comments I wrote in the code above directly.
For ```_.reduce```, someone who have no programming basic learning may be confused. Let's talk about it briefly, ```_.reduce``` is a function of the Underscore.js library. The FMZJS strategy supports this library, so it is very convenient for iterative calculation. The Underscore.js data link (https://underscorejs.net/#reduce)
The meaning is also very simple, for exmaple:
fungsi utama () { var arr = [1, 2, 3, 4] var sum = _.reducer (arr, fungsi (ret, ele) ret += ele
return ret
}, 0)
Log ((
That is, add up each number in the array ```[1, 2, 3, 4]```. Back to our strategy, we add up the trading volume values of each transaction record data in the ```trades``` array. Get a total of the latest transaction volume ```self.vol = 0.7 * self.vol + 0.3 * _.reduce (...)```, here we use ```...``` to replace the code. It is not difficult to see the calculation of ```self.vol``` is also a weighted average. That is, the newly generated trading volume accounts for 30% of the total, and the last weighted trading volume accounts for 70%. This ratio was set by the strategy author artificially and it may be related to the market rules.
As for your question, what if the interface to obtain the latest transaction data returned to the duplicate old data, then the data I got was wrong, and won't it be meaningful? Don't worry. This problem was considered in the strategy design, so the code has:
jika ((trade.Id > self.lastTradeId) maka (trade.Id == 0 && trade.Time > self.lastTradeId)) { ... Aku tidak tahu. {\cH00FFFF}
the judgement. It can be judged based on the transaction ID in the transaction record. Accumulation is triggered only when the ID is greater than the ID of the last record, or if the exchange interface does not provide an ID, that is, ```trade.Id == 0```, use the timestamp in the transaction record to judge. At this time, ```self.lastTradeId``` stores the timestamp of the transaction record instead of the ID.
The second function added:
self.updateOrderBook = function() {
var orderBook = _C(exchange.GetDepth)
self.orderBook = orderBook
if (orderBook.Bids.length < 3 || orderBook.Asks.length < 3) {
return
}
self.bidPrice = orderBook.Bids[0].Price * 0.618 + orderBook.Asks[0].Price * 0.382 + 0.01
self.askPrice = orderBook.Bids[0].Price * 0.382 + orderBook.Asks[0].Price * 0.618 - 0.01
self.prices.shift()
self.prices.push(_N((orderBook.Bids[0].Price + orderBook.Asks[0].Price) * 0.35 +
(orderBook.Bids[1].Price + orderBook.Asks[1].Price) * 0.1 +
(orderBook.Bids[2].Price + orderBook.Asks[2].Price) * 0.05))
}
Next, let's look at the function ```updateOrderBook```. From the name of the function, we can see that it is used to update the order book. However, it doesn't update the order book only. The function starts to call the FMZ API function ```GetDepth()``` to obtain the current market order book data (sell one... sell n, buy one... buy n), and record the order book data in ```self.orderBook```. Next, judge if the purchase order and sales order of the order book data are less than 3, if so, the invalid function will be returned directly.
After that, two pieces of data are calculated:
· Calculate the bill of lading price
The bill of lading price is also calculated by using the weighted average method. When calculating the purchase order, the weight given to the purchase price closest to the transaction price is 61.8% (0.618), and the weight given to the selling price closest to the transaction price is 38.2% (0.382)
When calculating the bill of lading bill of sale price, the same weight is given to the selling price closest to the transaction price. As for why is 0.618, it may be that the author prefers the golden section ratio. As for the last price (0.01), it is to offset to the center of the opening slightly.
· Update the weighted average price of the first three level of the order book on the time series
For the first three levels of purchase and sales order prices in the order book, the weighted average is calculated. The weight of the first level is 0.7, the weight of the second level is 0.2, and the weight of the third level is 0.1. Someone may say, "Oh, no, there are 0.7, 0.2, 0.1 in the code."
Let's expand the calculation:
(Beli satu + Jual satu) * 0,35 + ((Beli dua + Jual dua) * 0,1 + ((Beli tiga + Jual tiga) * 0,05
->
(Beli satu + jual satu) / 2 * 2 * 0,35 + ((Beli dua + jual dua) / 2 * 2 * 0,1 + ((Beli tiga + jual tiga) / 2 * 2 * 0,05
->
(Beli satu + jual satu) / 2 * 0,7 + ((Beli dua + jual dua) / 2 * 0,2 + ((Beli tiga + jual tiga) / 2 * 0,1
->
Harga rata-rata tingkat pertama * 0,7+harga rata-rata tingkat kedua * 0,2+harga rata-rata tingkat ketiga * 0,1
As we can see here, the final calculated price is actually a response to the price position of the middle of the third opening in the current market.
Then use this calculated price to update the array
self.prices, kicking out one of the oldest data (through the
Shift ((()function) and updating one of the newest data into it(through the
mendorong ((()function, shift and push functions are methods of the JS language array object, you can check the JS data for details). Thus forming the array
self.prices
Jadi mari kita beristirahat di sini, dan kita akan melihat Anda edisi berikutnya ~