Type/to search
8
Follow
1361
Followers
Phân tích Chiến lược thu hoạch tỏi tây (1)
HFT
Created 2020-11-12 22:11:32  Updated 2024-12-06 22:20:54
 26
 11230

img

Phân tích chiến lược thu hoạch tỏi tây

Gần đây, người phát minh ra cuộc thảo luận nhóm WeChat định lượngprint moneyCuộc thảo luận về robot diễn ra rất sôi nổi và một chiến lược rất cũ đã quay trở lại tầm nhìn của các nhà phân tích định lượng:Máy gặt tỏi tây
print moneyNguyên lý giao dịch của robot dựa trên chiến lược thu hoạch tỏi tây. Tôi tự trách mình vì đã không hiểu rõ chiến lược thu hoạch tỏi tây vào thời điểm đó. Vì vậy, tôi đã xem xét lại cẩn thận chiến lược ban đầu và cũng xem xét lại phiên bản được chuyển sang Inventor Quant.Máy thu hoạch tỏi tây OKCoin cấy ghép
Chúng ta hãy lấy phiên bản cấy ghép của chiến lược thu hoạch tỏi tây của Nền tảng định lượng Inventor để phân tích chiến lược và khám phá những ý tưởng đằng sau chiến lược. Để người dùng nền tảng có thể tìm hiểu ý tưởng chiến lược này.
Trong bài viết này, chúng ta sẽ phân tích sâu hơn dưới góc độ tư duy chiến lược, ý định, v.v. và cố gắng giảm bớt nội dung nhàm chán liên quan đến lập trình.

[[Cấy ghép OKCoin Leek Harvester] Mã nguồn chiến lược:

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

Tổng quan về chiến lược

Nhìn chung, khi bạn có một chiến lược để học, khi đọc nó, trước tiên bạn nên xem xét cấu trúc chương trình tổng thể. Chiến lược này không có nhiều mã, chỉ có chưa đến 200 dòng mã, rất súc tích, mức độ phục hồi về chiến lược ban đầu rất cao, về cơ bản là giống nhau. Mã chính sách chạy từmain()Chức năng bắt đầu thực thi và toàn bộ mã chiến lược đượcmain(), là mộtLeeksReaper()Chức năng,LeeksReaper()Hàm này cũng dễ hiểu. Hàm này có thể được hiểu là hàm tạo của mô-đun logic chiến lược thu hoạch tỏi tây (một đối tượng). Nói một cách đơn giảnLeeksReaper()Nó có trách nhiệm xây dựng logic giao dịch thu hoạch tỏi tây.

Từ khóa:
img
img

  • Chiến lượcmainDòng đầu tiên của hàm:
    var reaper = LeeksReaper(), mã khai báo một biến cục bộreaperSau đó gọi hàm LeeksReaper() để xây dựng một đối tượng logic chiến lược và gán nó choreaper

  • Chiến lượcmainChức năng tiếp theo là:

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

    Nhập mộtwhileVòng lặp chết, thực thi vô tậnreaperChức năng xử lý đối tượngpoll()poll()Chức năng này là logic chính của chiến lược giao dịch và toàn bộ chương trình chiến lược bắt đầu thực hiện logic giao dịch liên tục.
    Đối vớiSleep(TickInterval)Dòng này dễ hiểu. Nó dùng để kiểm soát thời gian tạm dừng sau mỗi lần thực hiện logic giao dịch tổng thể, nhằm kiểm soát tần suất luân chuyển của logic giao dịch.

Hồ sơLeeksReaper()Người xây dựng

hãy nhìn xemLeeksReaper()Cách các hàm xây dựng đối tượng logic chiến lược.

LeeksReaper()Khi bắt đầu hàm, một đối tượng rỗng được khai báo.var self = {},hiện hữuLeeksReaper()Trong quá trình thực hiện hàm, một số phương thức và thuộc tính sẽ được dần dần thêm vào đối tượng rỗng này, cuối cùng việc xây dựng đối tượng này sẽ hoàn tất và cuối cùng đối tượng này sẽ được trả về (tức làmain()Bên trong chức năngvar reaper = LeeksReaper()Trong bước này, đối tượng trả về được gán choreaper)。

Đưa choselfThêm thuộc tính vào một đối tượng

Tiếp theo choselfRất nhiều thuộc tính đã được thêm vào. Tôi sẽ mô tả từng thuộc tính bên dưới để bạn có thể nhanh chóng hiểu được mục đích và ý định của các thuộc tính và biến này, cũng như dễ dàng hiểu được chiến lược, để tránh bị nhầm lẫn khi bạn nhìn thấy đống mã này.

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 # 记录当前收益数值

Đưa choselfPhương pháp thêm đối tượng

Sau khi thêm các thuộc tính này vào bản thân, hãy bắt đầuselfViệc thêm phương thức vào một đối tượng cho phép đối tượng đó thực hiện một số công việc và có một số chức năng.

Chức năng đầu tiên được thêm vào:

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

updateTradesMục đích của chức năng này là thu thập dữ liệu giao dịch thị trường mới nhất, thực hiện một số tính toán dựa trên dữ liệu và ghi lại chúng để sử dụng trong logic tiếp theo của chiến lược.
Tôi đã viết các bình luận từng dòng trực tiếp trong mã ở trên.
_.reduceHọc sinh không có nền tảng lập trình có thể bị bối rối. Sau đây là lời giải thích ngắn gọn._.reduceĐúngUnderscore.jsCác chức năng của thư viện này được hỗ trợ bởi chiến lược FMZJS, do đó rất thuận tiện khi sử dụng các phép tính lặp.Liên kết dữ liệu Underscore.js

Ý nghĩa cũng rất đơn giản, ví dụ:

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 }

Đó là mảng[1, 2, 3, 4]Cộng tất cả các số trong . Quay lại chiến lược của chúng tôi,tradesGiá trị khối lượng giao dịch của mỗi bản ghi giao dịch trong mảng được cộng lại. Nhận hồ sơ giao dịch mới nhất và tổng khối lượng giao dịch.self.vol = 0.7 * self.vol + 0.3 * _.reduce(...)Xin vui lòng cho phép tôi sử dụng...Thay vì một đống mã đó. Không khó để thấy ở đây rằngself.volPhép tính này cũng là phép tính trung bình có trọng số. Nghĩa là tổng khối lượng giao dịch mới nhất chiếm 30% trọng số, khối lượng giao dịch thu được theo phép tính trọng số trước đó chiếm 70%. Tỷ lệ này được tác giả chiến lược thiết lập một cách nhân tạo và có thể liên quan đến việc quan sát các mô hình thị trường.
Còn câu hỏi của bạn, nếu giao diện lấy dữ liệu giao dịch mới nhất trả về dữ liệu cũ trùng lặp cho tôi thì sao? Khi đó dữ liệu tôi nhận được sẽ sai, vậy có ích gì khi sử dụng nó không? Đừng lo lắng, vấn đề này đã được tính đến khi thiết kế chiến lược, do đó nó được đưa vào mã.

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

Phán quyết này. Có thể đánh giá dựa trên ID giao dịch trong hồ sơ giao dịch. Tích lũy chỉ được kích hoạt khi ID lớn hơn ID của hồ sơ cuối cùng hoặc nếu giao diện trao đổi không cung cấp ID, nghĩa làtrade.Id == 0, sử dụng dấu thời gian trong hồ sơ giao dịch để xác định, tại thời điểm nàyself.lastTradeIdNhững gì được lưu trữ là dấu thời gian của bản ghi giao dịch chứ không phải là ID.

Chức năng bổ sung thứ hai:

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

Đồng hồ tiếp theoupdateOrderBookĐúng như tên gọi, chức năng này được sử dụng để cập nhật sổ lệnh. Tuy nhiên, nó không chỉ đơn thuần là cập nhật sổ lệnh. Chức năng này bắt đầu gọi hàm API của FMZGetDepth()Lấy dữ liệu sổ lệnh thị trường hiện tại (bán một...bán n, mua một...mua n) và ghi lại dữ liệu sổ lệnh trongself.orderBookở giữa. Tiếp theo, nếu dữ liệu sổ lệnh chứa ít hơn 3 lệnh mua và bán, hàm này được coi là không hợp lệ và trả về trực tiếp.

Sau đó, hai dữ liệu đã được tính toán:

  • Tính giá vận đơn
    Giá vận đơn cũng được tính bằng cách sử dụng giá trị trung bình có trọng số. Khi tính toán lệnh mua, lệnh mua được đưa ra trọng số lớn hơn là 61,8% (0,618) và lệnh bán chiếm trọng số còn lại là 38,2% (0,382).
    Điều tương tự cũng được áp dụng khi tính giá bán theo vận đơn, trong đó giá bán được tính trọng số lớn hơn. Về lý do tại sao lại là 0,618, có thể là do tác giả thích tỷ lệ vàng hơn. Đối với mức tăng hoặc giảm giá nhẹ cuối cùng (0,01), mục đích là dịch chuyển nhẹ giá về phía trung tâm thị trường.

  • Cập nhật giá trung bình có trọng số của ba bậc đầu tiên của sổ lệnh theo chuỗi thời gian
    Tính toán trung bình có trọng số được thực hiện trên ba bậc đầu tiên của giá lệnh mua và bán trong sổ lệnh, trong đó bậc đầu tiên có trọng số là 0,7, bậc thứ hai có trọng số là 0,2 và bậc thứ ba có trọng số là 0,1 . Một số học sinh có thể nói: "Ồ, không đúng, không có 0,7, 0,2, 0,1 trong mã"
    Chúng ta hãy mở rộng phép tính:

    (买一 + 卖一) * 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

    Ở đây bạn có thể thấy rằng giá tính toán cuối cùng thực sự phản ánh vị trí giá trung bình của ba mức trên thị trường hiện tại.
    Sau đó sử dụng giá đã tính toán này để cập nhậtself.pricesMảng, loại bỏ dữ liệu cũ nhất (thông quashift()chức năng), cập nhật dữ liệu mới nhất (thông quapush()Hàm, hàm shift và hàm push là các phương thức của đối tượng mảng ngôn ngữ JS. Bạn có thể kiểm tra thông tin JS để biết chi tiết). Do đó hình thànhself.pricesMảng là luồng dữ liệu được sắp xếp theo chuỗi thời gian.

À, uống nước đi, tôi dừng ở đây thôi, hẹn gặp lại lần sau~

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)