Analisis Strategi Pengumpul Keuntungan (1)

Penulis:Ninabadass, Dicipta: 2022-04-26 11:31:51, Dikemas kini:

Analisis Strategi Pengumpul Keuntungan (1)

Baru-baru ini, pengguna dalam kumpulan FMZ Quant WeChat membincangkan mengenai botprint moneyPerbincangan itu sangat panas, dan strategi yang sangat lama telah memasuki semula visi kuant:keuntungan Pengumpul. Prinsip perdagangan botprint moneySaya 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)
    }
}

Ringkasan Strategi

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 pertamamainfungsi dalam strategi:var reaper = LeeksReaper(); kod menyatakan pembolehubah tempatanreaper, dan kemudian memanggil fungsi LeeksReaper() untuk membina objek logik strategi dan menetapkannya kepadareaper.

  • Bahagian berikutmainfungsi:

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

    Masukkanwhilegelung tanpa henti, terus menjalankan fungsi pemprosesanreaperobjekpoll(), 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.

Menganalisis PembinaLeeksReaper()

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

Tambah atribut kepada objekself

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  

Tambah kaedah ke objekself

Selepas menambah sifat-sifat ini kepada self, mula menambah kaedah kepadaselfobjek, 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)

    }

FungsiupdateTradesadalah 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._.reduceadalah 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 dalamtradesarray, 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.voladalah 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 apa yang harus saya lakukan jika antara muka untuk mendapatkan data pelaksanaan terkini mengembalikan data lama yang berulang? maka, data yang diperoleh adalah semua salah, jadi adakah ada makna untuk digunakan? "Jangan risau, strategi telah direka dengan itu dalam fikiran, jadi kod mempunyai kandungan berikut:

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.lastTradeIdadalah 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 lihatupdateOrderBookfungsi. 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.orderBookKemudian, 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: Oh, salah, tidak ada 0.7, 0.2, atau 0.1 dalam kod!

    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.pricesarray, menendang keluar data tertua (olehshift()fungsi), dan mengemas kini dengan data terkini (olehpush()fungsi; shift dan push fungsi adalah semua kaedah objek array JS, anda boleh mencari bahan yang berkaitan dengan JS untuk maklumat lanjut).self.pricesadalah aliran data dalam urutan siri masa.

    Mari kita selesaikan analisis di sini, dan jumpa lain kali!


Lebih lanjut