Analisis Strategi Pengumpul Keuntungan (1)

Penulis:Ninabadass, Dibuat: 2022-04-26 11:31:51, Diperbarui:

Analisis Strategi Pengumpul Keuntungan (1)

Baru-baru ini, pengguna di grup FMZ Quant WeChat membahas tentang botprint moneyDiskusi sangat panas, dan strategi yang sangat tua telah kembali memasuki visi kuant:keuntungan HarvesterAku tidak tahu. Prinsip perdagangan bot dariprint moneyaku menyalahkan diriku sendiri karena tidak mengerti strategi profit harvester terlalu baik pada saat itu jadi, aku membaca strategi asli lagi dengan serius dan juga membaca versi ported:Porting OKCoin Profit HarvesterAku tidak tahu. Mengambil versi ported dari strategi profit harvester FMZ sebagai contoh, kami akan menganalisis strategi ini dan menggali ide-ide strategi, sehingga pengguna platform kami dapat mempelajari ide strategi ini. Dalam artikel ini, kita menganalisis lebih dari tingkat pemikiran strategis, niat, dll, untuk meminimalkan konten membosankan yang terkait dengan pemrograman.

[Port OKCoin Profit Harvester] Strategi Kode Sumber:

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)
    }
}

Gambaran Umum Strategi

Secara umum, ketika Anda mendapatkan strategi untuk dipelajari, ketika membaca, pertama-tama lihat pada keseluruhan struktur program. kode strategi tidak panjang, kurang dari 200 baris; dapat dikatakan sangat disederhanakan, dan memiliki pemulihan tinggi untuk strategi versi asli, yang pada dasarnya sama dengan yang satu ini.main()seluruh kode strategi, kecualimain(), hanya memiliki fungsi bernamaLeeksReaper().LeeksReaper()fungsi ini juga sangat mudah dipahami. fungsi ini dapat dipahami sebagai konstruktor dari modul logika strategi profit harvester (objek).LeeksReaper()bertanggung jawab untuk membangun logika perdagangan profit harvester.

  • Baris pertama darimainfungsi dalam strategi:var reaper = LeeksReaper(); kode menyatakan variabel lokalreaper, dan kemudian memanggil fungsi LeeksReaper() untuk membangun obyek logika strategi dan menugaskannya kereaper.

  • Bagian berikutnya darimainFungsi:

    while (true) {
        reaper.poll()
        Sleep(TickInterval)
    }
    

    Masukkan awhileloop tak terbatas, terus menjalankan fungsi pemrosesan darireaperobjekpoll(), danpoll()fungsi adalah logika utama dari strategi, sehingga seluruh program strategi mulai terus menjalankan logika perdagangan.

    Sedangkan untuk garis dariSleep(TickInterval), mudah dimengerti, yang adalah untuk mengontrol waktu jeda setelah setiap pelaksanaan keseluruhan logika perdagangan, dan yang tujuannya adalah untuk mengontrol frekuensi rotasi logika perdagangan.

Menganalisis Sang PembangunLeeksReaper()

Mari kita lihat bagaimana fungsiLeeksReaper()Membangun objek dalam logika strategi.

PeraturanLeeksReaper()fungsi dimulai dan menyatakan objek nol,var self = {}. Selama pelaksanaanLeeksReaper()fungsi, objek null ini akan secara bertahap dimodifikasi dengan menambahkan beberapa metode dan atribut di dalamnya.var reaper = LeeksReaper()dalammain()fungsi, dan objek yang dikembalikan ditugaskan untukreaper).

Tambahkan atribut ke objekself

Selanjutnya, saya menambahkan banyak atribut untukselfAnda dapat dengan cepat memahami tujuan dan maksud dari atribut dan variabel ini, yang akan memudahkan untuk memahami strategi, dan menghindari bingung ketika Anda melihat tumpukan kode.

    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  

Tambahkan metode ke objekself

Setelah menambahkan atribut ini ke self, mulai menambahkan metode keselfobjek, sehingga objek ini dapat melakukan beberapa operasi dan memiliki 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)

    }

Fungsi dariupdateTradesadalah untuk mendapatkan data eksekusi terbaru di pasar, dan untuk melakukan beberapa perhitungan dan catatan sesuai dengan data, dan memberikan hasil untuk digunakan dalam logika strategi berikutnya. Saya langsung menulis komentar baris demi baris dalam kode di atas. Siswa yang mungkin tidak memiliki dasar pemrograman akan bingung tentang_.reduce, jadi inilah pengantar singkatnya._.reduceadalah fungsi dariUnderscore.jslibrary, didukung oleh strategi FMZJS. Oleh karena itu, sangat nyaman untuk menggunakannya untuk melakukan perhitungan iteratif.link informasi dari Underscore.js

Sangat mudah dimengerti, misalnya:

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 penjumlahan setiap angka dalam array, yaitu[1, 2, 3, 4]Kembali ke strategi kami, adalah untuk menambahkan nilai volume dari setiap data rekaman eksekusi ditradesarray, menghasilkan total volume eksekusi terbaru.self.vol = 0.7 * self.vol + 0.3 * _.reduce(...), izinkan saya untuk mengganti bagian kode dengan...Tidak sulit untuk melihat di sini bahwa perhitunganself.voladalah juga rata-rata tertimbang. yaitu, volume eksekusi total yang baru dihasilkan menyumbang 30%, dan volume eksekusi yang dihitung sebelumnya menyumbang 70%. rasio ini secara artifisial ditetapkan oleh pengembang strategi, yang mungkin terkait dengan pengamatan aturan pasar.

Adapun pertanyaan Anda apa yang harus saya lakukan jika antarmuka untuk mendapatkan data eksekusi terbaru mengembalikan saya duplikat data lama? maka, data yang diperoleh adalah semua salah, jadi apakah ada arti untuk menggunakan?" Jangan khawatir, strategi telah dirancang dengan itu dalam pikiran, sehingga kode memiliki isi berikut:

if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
    ...
}

Penghakiman dapat didasarkan pada ID eksekusi dalam catatan eksekusi perdagangan. hanya ketika ID lebih besar dari ID terakhir kali, akumulasi dipicu. atau, jika antarmuka platform tidak memberikan ID, yaitu,trade.Id == 0, menggunakan time stamp dalam catatan eksekusi perdagangan untuk menilai.self.lastTradeIdadalah timestamp dari catatan eksekusi, bukan ID.

Fungsi Kedua Ditambahkan:

    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))
    }

Selanjutnya, mari kita lihatupdateOrderBookfungsi. Dari arti nama fungsi, dapat dilihat bahwa fungsi adalah untuk memperbarui buku pesanan. Namun, tidak hanya memperbarui buku pesanan. Fungsi, pada awalnya memanggil fungsi FMZ APIGetDepth()untuk memperoleh data buku pesanan pasar saat ini (menjual 1...menjual n, membeli 1...membeli n), dan mencatat data buku pesanan diin self.orderBookSelanjutnya, ia menilai apakah tingkat data buku pesanan untuk pesanan beli dan pesanan jual kurang dari 3 tingkat; jika demikian, ia dinilai sebagai fungsi yang tidak valid, dan mengembalikan secara langsung.

Setelah itu, dua perhitungan data dilakukan:

  • Menghitung harga pesanan pengiriman Perhitungan harga pengiriman juga didasarkan pada perhitungan rata-rata tertimbang. Saat menghitung harga penawaran pesanan beli, berikan bobot yang lebih besar pada harga beli 1, yaitu 61,8% (0,618), dan harga jual 1 menyumbang bobot yang tersisa sebesar 38,2% (0,382). Hal yang sama berlaku saat menghitung harga permintaan pesanan pengiriman, memberikan lebih banyak bobot pada harga jual 1.

  • Pembaruan harga rata-rata tertimbang dari tiga tingkat pertama dari buku pesanan pada seri waktu Untuk tiga tingkat pertama pesanan beli dan penjualan di buku pesanan, perhitungan rata-rata tertimbang dilakukan. Berat tingkat pertama adalah 0,7, berat tingkat kedua adalah 0,2, dan berat tingkat ketiga adalah 0,1.

    Mari kita lihat perhitungan yang diperluas:

    (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
    

    Di sini dapat dilihat bahwa harga yang dihitung akhir sebenarnya mencerminkan posisi harga rata-rata tingkat ketiga di pasar saat ini. Kemudian gunakan harga yang dihitung ini untuk memperbaruiself.pricesarray, tendang keluar data tertua (olehshift()fungsi), dan memperbarui dengan data terbaru (olehpush()fungsi; shift dan push fungsi adalah semua metode dari objek array JS, Anda dapat mencari materi terkait JS untuk rincian lebih lanjut).self.pricesadalah aliran data dalam urutan deret waktu.

    Mari kita selesaikan analisisnya di sini, dan sampai jumpa lain kali!


Lebih banyak