Baru-baru ini, pengguna di grup FMZ Quant WeChat membahas tentang botprint money
Diskusi sangat panas, dan strategi yang sangat tua telah kembali memasuki visi kuant:keuntungan HarvesterAku tidak tahu.
Prinsip perdagangan bot dariprint money
aku 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)
}
}
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 darimain
fungsi dalam strategi:var reaper = LeeksReaper()
; kode menyatakan variabel lokalreaper
, dan kemudian memanggil fungsi LeeksReaper() untuk membangun obyek logika strategi dan menugaskannya kereaper
.
Bagian berikutnya darimain
Fungsi:
while (true) {
reaper.poll()
Sleep(TickInterval)
}
Masukkan awhile
loop tak terbatas, terus menjalankan fungsi pemrosesan darireaper
objekpoll()
, 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.
LeeksReaper()
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
).
self
Selanjutnya, saya menambahkan banyak atribut untukself
Anda 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
self
Setelah menambahkan atribut ini ke self
objek, 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 dariupdateTrades
adalah 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._.reduce
adalah 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 ditrades
array, 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.vol
adalah 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
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.lastTradeId
adalah 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 lihatupdateOrderBook
fungsi. 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.orderBook
Selanjutnya, 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.prices
array, tendang keluar data tertua (olehshift()
fungsi), dan memperbarui dengan data terbaru (olehpush()
fungsi; self.prices
adalah aliran data dalam urutan deret waktu.
Mari kita selesaikan analisisnya di sini, dan sampai jumpa lain kali!