Baru-baru ini, pengguna dalam kumpulan FMZ Quant WeChat membincangkan mengenai botprint money
Perbincangan itu sangat panas, dan strategi yang sangat lama telah memasuki semula visi kuant:keuntungan Pengumpul.
Prinsip perdagangan botprint money
Saya menyalahkan diri sendiri kerana tidak memahami strategi pengumpul keuntungan dengan baik pada masa itu. jadi, saya membaca semula strategi asal dengan serius, dan juga membaca versi yang dipindahkan:Memindahkan OKCoin Profit Harvester- Tidak.
Mengambil versi pelaburan strategi pemanen keuntungan FMZ sebagai contoh, kami akan menganalisis strategi ini dan menggali idea strategi, supaya pengguna platform kami dapat mempelajari idea strategi ini.
Dalam artikel ini, kami menganalisis lebih banyak dari tahap pemikiran strategik, niat, dll., Untuk meminimumkan kandungan membosankan yang berkaitan dengan pengaturcaraan.
[Port OKCoin Keuntungan Harvester] Kod Sumber 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 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)
}
}
}
}
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 amnya, apabila anda mendapat strategi untuk belajar, ketika membaca, pertama melihat struktur program keseluruhan. Kod strategi tidak panjang, kurang daripada 200 baris; ia boleh dikatakan sangat disederhanakan, dan mempunyai pemulihan yang tinggi untuk versi strategi asal, yang pada dasarnya sama dengan ini.main()
keseluruhan kod strategi, kecualimain()
, hanya mempunyai fungsi bernamaLeeksReaper()
.LeeksReaper()
fungsi ini juga sangat mudah difahami. fungsi ini boleh difahami sebagai pembina modul logik strategi pemanen keuntungan (sebuah objek).LeeksReaper()
bertanggungjawab untuk membina logik perdagangan pemanen keuntungan.
Barisan pertamamain
fungsi dalam strategi:var reaper = LeeksReaper()
; kod menyatakan pembolehubah tempatanreaper
, dan kemudian memanggil fungsi LeeksReaper() untuk membina objek logik strategi dan menetapkannya kepadareaper
.
Bahagian berikutmain
fungsi:
while (true) {
reaper.poll()
Sleep(TickInterval)
}
Masukkanwhile
gelung tanpa henti, terus menjalankan fungsi pemprosesanreaper
objekpoll()
, danpoll()
fungsi adalah logik utama strategi, jadi keseluruhan program strategi mula terus melaksanakan logik perdagangan.
Bagi barisanSleep(TickInterval)
, adalah mudah difahami, yang adalah untuk mengawal masa rehat selepas setiap pelaksanaan logik perdagangan keseluruhan, dan yang tujuannya adalah untuk mengawal kekerapan putaran logik perdagangan.
LeeksReaper()
Mari kita lihat bagaimana fungsiLeeksReaper()
Membina objek dalam logik strategi.
PeraturanLeeksReaper()
fungsi bermula dan mengisytiharkan objek sifar,var self = {}
Semasa pelaksanaanLeeksReaper()
fungsi, objek null ini akan diubah secara beransur-ansur dengan menambah beberapa kaedah dan sifat di dalamnya.var reaper = LeeksReaper()
dalammain()
fungsi, dan objek yang dikembalikan diberikan kepadareaper
).
self
Seterusnya, saya menambah banyak ciri kepadaself
. Saya akan menerangkan setiap atribut di bawah. anda boleh dengan cepat memahami tujuan dan niat sifat-sifat dan pembolehubah ini, yang akan memudahkan untuk memahami strategi, dan mengelakkan menjadi keliru apabila anda melihat timbunan kod.
self.numTick = 0 # it is used to record the number of times that the trading is not triggered when the poll function is called. When placing an order is triggered and the order logic is executed, reset self.numTick to 0
self.lastTradeId = 0 # the trading record ID of the order that has been executed in the trading market; this variable records the current latest execution record ID in the market
self.vol = 0 # after the weighted average calculation, the trading volume reference of the market at each inspection (the market data is obtained once per time of the loop, which can be understood as the inspection of the market once)
self.askPrice = 0 # ask price of delivery order, which can be understood as the price of the pending sell order calculated by the strategy
self.bidPrice = 0 # bid price of delivery order
self.orderBook = {Asks:[], Bids:[]} # record the currently obtained order book data, that is, depth data (sell 1...sell n, buy 1...buy n)
self.prices = [] # an array that records the price in the time series after the weighted average calculation of the first three levels in the order book. Simply put, it is the weighted average price of the first three levels of the order book obtained by storing each time, and put them in an array as reference of the subsequent strategic trading signals. Therefore, the variable name is "prices", plural, which means a set of prices
self.tradeOrderId = 0 # record the order ID after currently lading and ordering
self.p = 0.5 # position proportion; when the currency value is exactly half of the total asset value, the value of it is 0.5, which means the balanced state
self.account = null # record the account asset data, which will be returned by the function GetAccount()
self.preCalc = 0 # record the timestamp when calculating the return of the latest time, in milliseconds, which is used to control the frequency of triggering execution of the return calculation code
self.preNet = 0 # record the current return value
self
Selepas menambah sifat-sifat ini kepada self
objek, supaya objek ini boleh melakukan beberapa operasi dan mempunyai beberapa fungsi.
Fungsi pertama yang ditambahkan:
self.updateTrades = function() {
var trades = _C(exchange.GetTrades) # call the encapsulated interface "GetTrades" of FMZ, to obtain the currently latest execution data in the market
if (self.prices.length == 0) { # when self.prices.length == 0, you need to fill values in the array of self.prices, which is only triggered when the strategy is started
while (trades.length == 0) { # if there is no latest execution record in the market, the while loop will run infinitely, until there is new execution data; the, update the variable "trades"
trades = trades.concat(_C(exchange.GetTrades)) # "concat" is a method of JS array type, which is used to match two arrays; here we use it to match the array "trades" and the array returned by "_C(exchange.GetTrades)" into one array
}
for (var i = 0; i < 15; i++) { # fill in values for "self.prices"; fill 15 latest execution prices
self.prices[i] = trades[trades.length - 1].Price
}
}
self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) { # _.reduce function iteratively calculates the accumulated execution volume of the latest execution record
// 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)
}
FungsiupdateTrades
adalah untuk mendapatkan data pelaksanaan terkini di pasaran, dan melakukan beberapa pengiraan dan rekod mengikut data, dan menyediakan hasil untuk digunakan dalam logik strategi berikutnya.
Saya telah langsung menulis komen baris demi baris dalam kod di atas.
Pelajar yang mungkin tidak mempunyai asas pengaturcaraan akan keliru tentang_.reduce
, jadi inilah pengenalan ringkas._.reduce
adalah fungsiUnderscore.jsPerpustakaan, disokong oleh strategi FMZJS. Oleh itu, ia adalah sangat mudah untuk menggunakannya untuk melakukan pengiraan berulang.pautan maklumat Underscore.js
Sangat mudah difahami, contohnya:
function main () {
var arr = [1, 2, 3, 4]
var sum = _.reduce(arr, function(ret, ele){
ret += ele
return ret
}, 0)
Log("sum:", sum) # sum is 10
}
Ini adalah menambah setiap nombor dalam array, iaitu[1, 2, 3, 4]
Kembali ke strategi kami, ia adalah untuk menambah nilai jumlah setiap data rekod pelaksanaan dalamtrades
array, menghasilkan jumlah jumlah pelaksanaan terakhir.self.vol = 0.7 * self.vol + 0.3 * _.reduce(...)
, izinkan saya untuk menggantikan bahawa bahagian kod dengan...
Ia tidak sukar untuk melihat di sini bahawa pengiraanself.vol
adalah juga purata tertimbang. iaitu, jumlah jumlah pelaksanaan yang baru dihasilkan menyumbang 30%, dan jumlah pelaksanaan yang dihitung sebelumnya menyumbang 70%. nisbah ini secara artifisial ditetapkan oleh pemaju strategi, yang mungkin berkaitan dengan pematuhan peraturan pasaran.
Mengenai soalan anda
if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
...
}
Penghakiman boleh berdasarkan ID pelaksanaan dalam rekod pelaksanaan dagangan. Hanya apabila ID lebih besar daripada ID kali terakhir, pengumpulan dicetuskan. Atau, jika antara muka platform tidak memberikan ID, iaitu,trade.Id == 0
, gunakan stempel masa dalam rekod pelaksanaan dagangan untuk menilai.self.lastTradeId
adalah cap masa rekod pelaksanaan, bukan ID.
Fungsi Tambahan Kedua:
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))
}
Seterusnya, mari kita lihatupdateOrderBook
fungsi. Dari makna nama fungsi, dapat dilihat bahawa fungsi adalah untuk mengemas kini buku pesanan. Walau bagaimanapun, ia tidak hanya mengemas kini buku pesanan. Fungsi, pada mulanya memanggil fungsi FMZ APIGetDepth()
untuk mendapatkan data buku pesanan pasaran semasa (menjual 1...menjual n, membeli 1...membeli n), dan merakam data buku pesanan dalamin self.orderBook
Kemudian, ia menilai jika tahap data buku pesanan untuk pesanan beli dan pesanan jual adalah kurang daripada 3 tahap; jika ya, ia dinilai sebagai fungsi yang tidak sah, dan kembali terus.
Selepas itu, dua pengiraan data dilakukan:
Mengira harga pesanan penghantaran Pengiraan harga penghantaran juga berdasarkan pengiraan purata tertimbang. Apabila mengira harga tawaran pesanan beli, berikan berat yang lebih besar kepada harga beli 1, iaitu 61.8% (0.618), dan harga jual 1 menyumbang kepada berat yang tersisa sebanyak 38.2% (0.382). Hal yang sama berlaku ketika mengira harga permintaan pesanan penghantaran, memberikan lebih banyak berat kepada harga jual 1.
Mengemas kini harga purata bertingkat tiga peringkat pertama buku pesanan pada siri masa
Untuk tiga peringkat pertama pesanan beli dan jual dalam buku pesanan, pengiraan purata tertimbang dilakukan. Berat tahap pertama adalah 0.7, berat tahap kedua adalah 0.2, dan berat tahap ketiga adalah 0.1. Beberapa pelajar mungkin berkata:
Mari kita lihat pengiraan yang diperluaskan:
(Buy 1 + Sell 1) * 0.35 + (Buy 2 + Sell 2) * 0.1 + (Buy 3 + Sell 3) * 0.05
->
(Buy 1 + Sell 1) / 2 * 2 * 0.35 + (Buy 2 + Sell 2) / 2 * 2 * 0.1 + (Buy 3 + Sell 3) / 2 * 2 * 0.05
->
(Buy 1 + Sell 1) / 2 * 0.7 + (Buy 2 + Sell 2) / 2 * 0.2 + (Buy 3 + Sell 3) / 2 * 0.1
->
average price of the first level * 0.7 + average price of the second level * 0.2 + average price of the third level * 0.1
Ia dapat dilihat di sini bahawa harga yang dihitung akhir sebenarnya mencerminkan kedudukan harga purata peringkat ketiga di pasaran semasa.
Kemudian gunakan harga yang dikira ini untuk mengemas kiniself.prices
array, menendang keluar data tertua (olehshift()
fungsi), dan mengemas kini dengan data terkini (olehpush()
fungsi; self.prices
adalah aliran data dalam urutan siri masa.
Mari kita selesaikan analisis di sini, dan jumpa lain kali!