Type/to search
8
Follow
1361
Followers
Analisis Strategi Penuai Leek (1)
HFT
Created 2020-11-12 22:11:32  Updated 2024-12-06 22:20:54
 26
 11229

img

Analisis Strategi Penuai Leek

Baru-baru ini, pencipta perbincangan kumpulan WeChat kuantitatifprint moneyPerbincangan tentang robot sangat hangat, dan strategi yang sangat lama memasuki semula visi quants:penuai daun bawang
print moneyPrinsip perdagangan robot menggunakan strategi penuai daun bawang Saya menyalahkan diri saya sendiri kerana tidak memahami strategi penuai daun bawang dengan jelas pada masa itu. Jadi, saya menyemak semula strategi asal dengan teliti, dan juga menyemak versi yang dipindahkan pada Quant Inventor.Pemindahan OKCoin Leek Harvester
Mari kita ambil versi pemindahan strategi penuai daun bawang bagi Platform Kuantitatif Pencipta untuk menganalisis strategi dan meneroka idea di sebaliknya. Supaya pengguna platform dapat mempelajari idea strategik ini.
Dalam artikel ini, kami akan menganalisis lebih banyak daripada perspektif pemikiran strategik, niat, dll., dan cuba mengurangkan kandungan membosankan yang berkaitan dengan pengaturcaraan.

[Transplant OKCoin Leek Harvester] Kod sumber strategi:

pine
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("开始平衡", 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("开始平衡", 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 Keseluruhan Strategi

Secara amnya, apabila anda mendapat strategi untuk belajar, apabila anda membacanya, anda harus terlebih dahulu melihat struktur program keseluruhan. Strategi ini tidak mempunyai banyak kod, hanya kurang daripada 200 baris kod, yang sangat ringkas, dan tahap pemulihan kepada strategi asal adalah sangat tinggi, dan ia pada asasnya sama. Kod dasar dijalankan daripadamain()Fungsi mula dilaksanakan, dan keseluruhan kod strategi adalahmain(), ialah aLeeksReaper()Fungsinya,LeeksReaper()Fungsi ini juga mudah difahami. Fungsi ini boleh difahami sebagai pembina modul logik strategi penuai daun bawang (sebuah objek).LeeksReaper()Ia bertanggungjawab untuk membina logik transaksi penuai daun bawang.

Kata kunci:
img
img

  • StrategimainBaris pertama fungsi:
    var reaper = LeeksReaper(), kod tersebut mengisytiharkan pembolehubah tempatanreaperKemudian panggil fungsi LeeksReaper() untuk membina objek logik strategi dan berikannyareaper

  • StrategimainFungsinya adalah seterusnya:

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

    Masukkan awhileGelung mati, pelaksanaan yang tidak berkesudahanreaperFungsi pemprosesan objekpoll()poll()Fungsi ini adalah logik utama strategi dagangan, dan keseluruhan program strategi mula melaksanakan logik dagangan secara berterusan.
    AdapunSleep(TickInterval)Baris ini mudah difahami. Ia adalah untuk mengawal masa jeda selepas setiap pelaksanaan logik transaksi keseluruhan, untuk mengawal kekerapan putaran logik transaksi.

PemprofilanLeeksReaper()Pembina

lihatlahLeeksReaper()Bagaimana fungsi membina objek logik strategi.

LeeksReaper()Pada permulaan fungsi, objek kosong diisytiharkan.var self = {}, wujudLeeksReaper()Semasa pelaksanaan fungsi, beberapa kaedah dan sifat akan ditambah secara beransur-ansur pada objek kosong ini, dan akhirnya pembinaan objek ini akan selesai, dan akhirnya objek ini akan dikembalikan (iaitu,main()Di dalam fungsivar reaper = LeeksReaper()Dalam langkah ini, objek yang dikembalikan diberikan kepadareaper)。

beriselfMenambah sifat pada objek

Seterusnya memberiselfBanyak sifat telah ditambah Saya akan menerangkan setiap sifat di bawah supaya anda boleh memahami dengan cepat tujuan dan niat sifat dan pembolehubah ini, dan memahami strategi dengan mudah, untuk mengelakkan kekeliruan apabila anda melihat timbunan kod ini.

python
self.numTick = 0 # 用来记录poll函数调用时未触发交易的次数,当触发下单并且下单逻辑执行完时,self.numTick重置为0 self.lastTradeId = 0 # 交易市场已经成交的订单交易记录ID,这个变量记录市场当前最新的成交记录ID self.vol = 0 # 通过加权平均计算之后的市场每次考察时成交量参考(每次循环获取一次市场行情数据,可以理解为考察了行情一次) self.askPrice = 0 # 卖单提单价格,可以理解为策略通过计算后将要挂卖单的价格 self.bidPrice = 0 # 买单提单价格 self.orderBook = {Asks:[], Bids:[]} # 记录当前获取的订单薄数据,即深度数据(卖一...卖n,买一...买n) self.prices = [] # 一个数组,记录订单薄中前三档加权平均计算之后的时间序列上的价格,简单说就是每次储存计算得到的订单薄前三档加权平均价格,放在一个数组中,用于后续策略交易信号参考,所以该变量名是prices,复数形式,表示一组价格 self.tradeOrderId = 0 # 记录当前提单下单后的订单ID self.p = 0.5 # 仓位比重,币的价值正好占总资产价值的一半时,该值为0.5,即平衡状态 self.account = null # 记录账户资产数据,由GetAccount()函数返回数据 self.preCalc = 0 # 记录最近一次计算收益时的时间戳,单位毫秒,用于控制收益计算部分代码触发执行的频率 self.preNet = 0 # 记录当前收益数值

beriselfKaedah Tambah Objek

Selepas menambah atribut ini pada diri sendiri, mulakanselfMenambah kaedah pada objek membolehkan objek melakukan beberapa kerja dan mempunyai beberapa fungsi.

Fungsi pertama ditambah:

cpp
self.updateTrades = function() { var trades = _C(exchange.GetTrades) # 调用FMZ封装的接口GetTrades,获取当前最新的市场成交数据 if (self.prices.length == 0) { # 当self.prices.length == 0时,需要给self.prices数组填充数值,只有策略启动运行时才会触发 while (trades.length == 0) { # 如果近期市场上没有更新的成交记录,这个while循环会一直执行,直到有最新成交数据,更新trades变量 trades = trades.concat(_C(exchange.GetTrades)) # concat 是JS数组类型的一个方法,用来拼接两个数组,这里就是把“trades”数组和“_C(exchange.GetTrades)”返回的数组数据拼接成一个数组 } for (var i = 0; i < 15; i++) { # 给self.prices填充数据,填充15个最新成交价格 self.prices[i] = trades[trades.length - 1].Price } } self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) { # _.reduce 函数迭代计算,累计最新成交记录的成交量 // 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) }

updateTradesTujuan fungsi ini adalah untuk mendapatkan data transaksi pasaran terkini, melakukan beberapa pengiraan berdasarkan data dan merekodkannya untuk digunakan dalam logik strategi berikutnya.
Saya menulis komen baris demi baris secara langsung dalam kod di atas.
untuk_.reducePelajar yang tidak mempunyai asas pengaturcaraan mungkin keliru. Berikut adalah penjelasan ringkas._.reduceyaUnderscore.jsFungsi perpustakaan ini disokong oleh strategi FMZJS, jadi ia sangat mudah digunakan untuk pengiraan berulang.Pautan data Underscore.js

Maksudnya juga sangat mudah, contohnya:

python
function main () { var arr = [1, 2, 3, 4] var sum = _.reduce(arr, function(ret, ele){ ret += ele return ret }, 0) Log("sum:", sum) # sum 等于 10 }

Iaitu, tatasusunan[1, 2, 3, 4]Tambahkan setiap nombor dalam . Berbalik kepada strategi kami,tradesNilai volum transaksi setiap rekod transaksi dalam tatasusunan dijumlahkan. Dapatkan rekod transaksi terkini dan jumlah volum transaksi.self.vol = 0.7 * self.vol + 0.3 * _.reduce(...)Tolong izinkan saya menggunakan...Daripada sekumpulan kod itu. Ia tidak sukar untuk melihat di siniself.volPengiraan juga adalah purata wajaran. Iaitu, jumlah volum transaksi terkini menyumbang 30% daripada berat, dan volum urus niaga yang diperolehi oleh pengiraan wajaran sebelumnya menyumbang 70%. Nisbah ini ditetapkan secara buatan oleh pengarang strategi dan mungkin berkaitan dengan memerhati corak pasaran.
Bagi soalan anda, bagaimana jika antara muka untuk mendapatkan data transaksi terkini mengembalikan data lama pendua kepada saya Kemudian data yang saya dapat akan salah, jadi adakah gunanya menggunakannya? Jangan risau, masalah ini telah diambil kira semasa mereka bentuk strategi, jadi ia termasuk dalam kod.

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

Penghakiman ini. Ia boleh dinilai berdasarkan ID transaksi dalam rekod transaksi dicetuskan hanya apabila ID lebih besar daripada ID rekod terakhir, atau jika antara muka pertukaran tidak memberikan ID, iaitu,trade.Id == 0, menggunakan cap masa dalam rekod transaksi untuk menentukan, pada masa iniself.lastTradeIdApa yang disimpan ialah cap masa rekod transaksi, bukan ID.

Fungsi tambahan kedua:

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

Tonton seterusnyaupdateOrderBookSeperti namanya, fungsi ini digunakan untuk mengemas kini buku pesanan. Walau bagaimanapun, ia lebih daripada sekadar mengemas kini buku pesanan. Fungsi ini mula memanggil fungsi API FMZGetDepth()Dapatkan data buku pesanan pasaran semasa (jual satu...jual n, beli satu...beli n), dan rekod data buku pesanan dalamself.orderBooktengah. Seterusnya, jika data buku pesanan mengandungi kurang daripada 3 pesanan beli dan jual, fungsi tersebut dianggap tidak sah dan kembali secara langsung.

Selepas itu, dua data dikira:

  • Kira harga bil muatan
    Harga bil muatan juga dikira menggunakan purata wajaran Apabila mengira pesanan belian, pesanan belian diberikan wajaran yang lebih besar iaitu 61.8% (0.618), dan pesanan jual menyumbang baki berat sebanyak 38.2% (0.382).
    Perkara yang sama berlaku apabila mengira harga jualan bil muatan, dengan harga jualan diberi berat yang lebih besar. Adapun mengapa ia 0.618, mungkin penulis lebih suka nisbah emas. Bagi kenaikan atau penurunan sedikit terakhir dalam harga (0.01), ia adalah untuk beralih sedikit ke arah tengah pasaran.

  • Kemas kini harga purata wajaran bagi tiga peringkat pertama buku pesanan pada siri masa
    Pengiraan purata wajaran dilakukan pada tiga peringkat pertama harga pesanan beli dan jual dalam buku pesanan, dengan peringkat pertama mempunyai berat 0.7, peringkat kedua mempunyai berat 0.2, dan peringkat ketiga mempunyai berat 0.1 . Sesetengah pelajar mungkin berkata: "Oh, itu tidak betul, tiada 0.7, 0.2, 0.1 dalam kod"
    Mari kembangkan pengiraan:

    (买一 + 卖一) * 0.35 + (买二 + 卖二) * 0.1 + (买三 + 卖三) * 0.05 -> (买一 + 卖一) / 2 * 2 * 0.35 + (买二 + 卖二) / 2 * 2 * 0.1 + (买三 + 卖三) / 2 * 2 * 0.05 -> (买一 + 卖一) / 2 * 0.7 + (买二 + 卖二) / 2 * 0.2 + (买三 + 卖三) / 2 * 0.1 -> 第一档平均的价格 * 0.7 + 第二档平均的价格 * 0.2 + 第三档平均的价格 * 0.1

    Di sini anda boleh melihat bahawa harga pengiraan akhir sebenarnya mencerminkan kedudukan harga pertengahan tiga tahap dalam pasaran semasa.
    Kemudian gunakan harga yang dikira ini untuk mengemas kiniself.pricesArray, tendang keluar data tertua (melaluishift()fungsi), kemas kini data terkini (melaluipush()Fungsi, anjakan dan tolak ialah kaedah objek tatasusunan bahasa JS Anda boleh menyemak maklumat JS untuk butiran). Dengan demikian membentukself.pricesTatasusunan ialah strim data tersusun siri masa.

Ehem, minum air, saya akan singgah di sini buat masa ini, jumpa lagi~

Related Recommendations
Comment
All comments (21)

    梦总你好,想问一下。self.prices 先填充的15个历史成交价格,然后又填充订单薄前三档加权平均价格. 对吧

    5 years ago

    是的。

    5 years ago

    我想给梦总点个赞

    5 years ago

    可惜了,很多交易所的做市商已经将盘口买一卖一价压到了只剩下一个tick,让策略里这种尝试插入到买一卖一中间的操作没有了意义

    6 years ago

    主要是学习思路,洞察这些可以高频的交易机会。

    6 years ago

    谢谢了,我写了python的版本在山寨币上跑了一下,真是手续费收割机啊。。。不到5分钟20刀没了:)

    6 years ago

    不客气,可以看下草神写的策略原理分析的文章,高频策略是需要一些支持的。

    6 years ago

    太棒了,没有梦总的解释还真的不能完全看懂,感谢大佬的耐心解释!

    6 years ago

    感谢支持~如果喜欢,帮忙分享下,嘿嘿~

    6 years ago

    黄金分割0.618 0.382是用的斐波
    梦总牛批

    6 years ago

    上学那会儿,学到这个黄金分割比例记得特别清楚,说是这样比例长宽的矩形最美~~但是不知道为什么。。。。

    6 years ago

    确实牛批啊这么详细

    6 years ago

    感谢支持!

    6 years ago

    大大的赞,这么详细的解答.感谢分享

    6 years ago

    感谢支持!

    6 years ago

    梦总牛p

    6 years ago

    感谢支持!

    6 years ago

    梦总,牛批!!
    虽然注释了,但是看起来还是非常复杂.....

    6 years ago

    其实不复杂,这次注释的比较啰嗦,逐行都尽量用最容易理解的方式描述了。通篇看下来应该理解不难。

    6 years ago

    梦总,牛批!!

    6 years ago

    感谢支持。

    6 years ago
  • 1
iPhone Download
Forums
PINE Language
© 2015 - ∞ INVENTOR PTE LTD (SG)