সম্প্রতি, FMZ Quant WeChat গ্রুপের ব্যবহারকারীরা বট সম্পর্কে আলোচনা করেছেন।print money
এই আলোচনা খুবই উত্তপ্ত ছিল এবং একটি পুরনো কৌশল আবারও কোয়ান্টের দৃষ্টিভঙ্গিতে ফিরে এসেছে:মুনাফা হার্ভেস্টার.
বট ট্রেডিং নীতিprint money
আমি নিজেকে দোষারোপ করি যে আমি সেই সময়ে লাভের জন্য কৌশলটি খুব ভালভাবে বুঝতে পারিনি। তাই, আমি মূল কৌশলটি আবার গুরুত্ব সহকারে পড়লাম, এবং পোর্টেড সংস্করণও পড়লামঃOKCoin মুনাফা হার্ভেস্টারের পোর্টিং..
উদাহরণস্বরূপ, এফএমজেডের মুনাফা কাটার কৌশলটির পোর্টেড সংস্করণ গ্রহণ করে, আমরা এই কৌশলটি বিশ্লেষণ করতে যাচ্ছি এবং কৌশলটির ধারণাগুলি খনন করতে যাচ্ছি, যাতে আমাদের প্ল্যাটফর্ম ব্যবহারকারীরা এই কৌশল ধারণাটি শিখতে পারে।
এই নিবন্ধে, আমরা প্রোগ্রামিং সম্পর্কিত বিরক্তিকর বিষয়বস্তু হ্রাস করার জন্য কৌশলগত চিন্তাভাবনা, অভিপ্রায় ইত্যাদির স্তর থেকে আরও বিশ্লেষণ করি।
[পোর্ট OKCoin মুনাফা হার্ভেস্টার] কৌশল সোর্স কোডঃ
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)
}
}
সাধারণভাবে, যখন আপনি একটি কৌশল শিখতে, যখন পড়া, প্রথম সামগ্রিক প্রোগ্রাম কাঠামো তাকান। কৌশল কোড দীর্ঘ নয়, কম 200 লাইন; এটি খুব সরলীকৃত বলা যেতে পারে, এবং মূল সংস্করণ কৌশল জন্য একটি উচ্চ পুনরুদ্ধার আছে, যা মূলত এই এক অনুরূপ। যখন কৌশল কোড চলমান হয়, এটি থেকে শুরু হয়main()
ফাংশন. সম্পূর্ণ কৌশল কোড, ব্যতীতmain()
, শুধুমাত্র একটি ফাংশন নামক আছেLeeksReaper()
.LeeksReaper()
এই ফাংশনটি লাভ হার্ভেস্টার কৌশল লজিক মডিউল (একটি বস্তু) এর কনস্ট্রাক্টর হিসাবে বোঝা যায়। সহজভাবে বলতে গেলে,LeeksReaper()
মুনাফা কাটার ব্যবসায়িক যুক্তি তৈরির জন্য দায়ী।
প্রথম লাইনটি হল-main
পরিকল্পনায় ভূমিকা:var reaper = LeeksReaper()
; কোডটি একটি স্থানীয় ভেরিয়েবল ঘোষণা করে ।reaper
, এবং তারপর একটি কৌশল লজিক অবজেক্ট নির্মাণ করতে LeeksReaper() ফাংশন কল এবং এটি নির্ধারণ করেreaper
.
পরবর্তী অংশেmain
ফাংশনঃ
while (true) {
reaper.poll()
Sleep(TickInterval)
}
একটি লিখুনwhile
অসীম লুপ, ক্রমাগত প্রক্রিয়াকরণ ফাংশন চালানোরreaper
বস্তুpoll()
, এবংpoll()
ফাংশন হল কৌশলটির মূল যুক্তি, তাই পুরো কৌশল প্রোগ্রামটি ট্রেডিং লজিককে ক্রমাগত বাস্তবায়ন করতে শুরু করে।
আর লাইনটা হলো-Sleep(TickInterval)
, এটা সহজেই বোঝা যায়, যা সামগ্রিক ট্রেডিং লজিকের প্রতিটি কার্যকরকরণের পরে বিরতি সময় নিয়ন্ত্রণ করে এবং যার উদ্দেশ্য হল ট্রেডিং লজিকের ঘূর্ণন ফ্রিকোয়েন্সি নিয়ন্ত্রণ করা।
LeeksReaper()
আসুন দেখি কিভাবে ফাংশনLeeksReaper()
কৌশল যুক্তিতে একটি বস্তু তৈরি করে।
দ্যLeeksReaper()
ফাংশন শুরু হয় এবং একটি শূন্য বস্তু ঘোষণা করে,var self = {}
.LeeksReaper()
ফাংশন, এই শূন্য বস্তু ধীরে ধীরে এটি কিছু পদ্ধতি এবং বৈশিষ্ট্য যোগ করে সংশোধন করা হবে. অবশেষে, বস্তুর নির্মাণ সম্পন্ন হয়, এবং বস্তুর ফিরে আসে (অর্থাৎ,var reaper = LeeksReaper()
মধ্যেmain()
ফাংশন, এবং ফিরে বস্তুর নির্ধারিত হয়reaper
).
self
এরপর, আমি অনেক বৈশিষ্ট্য যোগ করেছিself
. আমি নীচে প্রতিটি বৈশিষ্ট্য বর্ণনা করব। আপনি দ্রুত এই বৈশিষ্ট্য এবং ভেরিয়েবলগুলির উদ্দেশ্য এবং উদ্দেশ্য বুঝতে পারেন, যা কৌশলটি বুঝতে সহজ করে তুলবে এবং আপনি কোডের স্তূপটি দেখলে বিভ্রান্ত হওয়া এড়াতে পারে।
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
self
অবজেক্ট, যাতে এই অবজেক্ট কিছু অপারেশন করতে পারে এবং কিছু ফাংশন থাকতে পারে।
প্রথম যোগ করা ফাংশনঃ
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)
}
এর কার্যকারিতাupdateTrades
বাজারের সর্বশেষ কার্যকর তথ্য সংগ্রহ করা, কিছু গণনা করা এবং তথ্য অনুযায়ী রেকর্ড করা এবং পরবর্তী কৌশল যুক্তিতে ব্যবহারের জন্য ফলাফল সরবরাহ করা।
উপরের কোডে আমি সরাসরি মন্তব্যগুলি লাইন দ্বারা লাইন লিখেছি।
যেসব শিক্ষার্থীর কাছে প্রোগ্রামিং ফাউন্ডেশন নেই তারা হয়তো বিভ্রান্ত হবে।_.reduce
, তাই এখানে একটি সংক্ষিপ্ত ভূমিকা._.reduce
এর ফাংশনUnderscore.jsলাইব্রেরি, FMZJS কৌশল দ্বারা সমর্থিত. অতএব, এটি পুনরাবৃত্তিমূলক গণনা করতে এটি ব্যবহার করা খুব সুবিধাজনক.Underscore.js-এর তথ্য লিঙ্ক
খুব সহজেই বোঝা যায়, উদাহরণস্বরূপঃ
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
}
এটি অ্যারেতে প্রতিটি সংখ্যা যোগ করছে, যথা[1, 2, 3, 4]
. আমাদের কৌশল ফিরে, এটি প্রতিটি এক্সিকিউশন রেকর্ড তথ্য ভলিউম মান যোগ করা হয়trades
অ্যারে, ফলাফল সর্বশেষ এক্সিকিউশন ভলিউম মোট.self.vol = 0.7 * self.vol + 0.3 * _.reduce(...)
, আমাকে কোডের এই অংশটি দিয়ে প্রতিস্থাপন করার অনুমতি দিন...
এখানে এটা দেখা কঠিন নয় যে হিসাবself.vol
এটিও একটি ওজনযুক্ত গড়, অর্থাৎ, নতুন উত্পন্ন মোট এক্সিকিউশন ভলিউম 30% এবং পূর্ববর্তী এক্সিকিউশন ভলিউম গণনা করা 70% এর জন্য অ্যাকাউন্ট। এই অনুপাতটি কৌশল বিকাশকারী দ্বারা কৃত্রিমভাবে সেট করা হয়, যা বাজারের নিয়মের সাথে সম্পর্কিত হতে পারে।
আপনার প্রশ্নের ক্ষেত্রে
if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
...
}
বিচার ট্রেডিং এক্সিকিউশন রেকর্ডে এক্সিকিউশন আইডি ভিত্তিতে করা যেতে পারে। শুধুমাত্র যখন আইডি শেষবারের আইডি থেকে বড় হয়, তখনই জমে যাওয়া শুরু হয়। অথবা, যদি প্ল্যাটফর্ম ইন্টারফেস একটি আইডি প্রদান না করে, অর্থাৎ, যদি আপনি একটি আইডি ব্যবহার করেন তবে আপনি একটি আইডি ব্যবহার করতে পারেন।trade.Id == 0
, বিচার করার জন্য ট্রেডিং এক্সিকিউশন রেকর্ডে টাইমস্ট্যাম্প ব্যবহার করুন। এই সময়ে, কি দ্বারা সংরক্ষিত হয়self.lastTradeId
হ'ল এক্সিকিউশন রেকর্ডের টাইমস্ট্যাম্প, আইডি নয়।
দ্বিতীয় যোগ করা ফাংশনঃ
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))
}
এরপরে, আসুন দেখুনupdateOrderBook
ফাংশন নামের অর্থ থেকে দেখা যায় যে ফাংশনটি অর্ডার বুক আপডেট করা। তবে এটি কেবল অর্ডার বুক আপডেট করে না। ফাংশনটি প্রথমে এফএমজেড এপিআই ফাংশনকে কল করেGetDepth()
বর্তমান বাজার অর্ডার বুকের তথ্য (বিক্রয় 1...বিক্রয় n, কিনুন 1...ক্রয় n) পেতে এবং অর্ডার বুকের তথ্য রেকর্ড করতেin self.orderBook
. এরপরে, এটি মূল্যায়ন করে যে ক্রয় আদেশ এবং বিক্রয় আদেশের জন্য অর্ডার বুকের ডেটা স্তরটি 3 স্তরের কম কিনা; যদি তাই হয় তবে এটি একটি অবৈধ ফাংশন হিসাবে বিবেচিত হয় এবং সরাসরি ফিরে আসে।
এর পর, দুটি ডেটা গণনা করা হয়েছিলঃ
ডেলিভারি অর্ডার মূল্য গণনা করুন ডেলিভারি মূল্যের গণনাও ওজনযুক্ত গড় গণনার উপর ভিত্তি করে করা হয়। একটি ক্রয় আদেশের বিড মূল্য গণনা করার সময়, কিনুন 1 দামকে একটি বড় ওজন দিন, যথা 61.8% (0.618) এবং বিক্রয় 1 দামটি বাকী 38.2% (0.382) এর ওজনকে অ্যাকাউন্ট করে। ডেলিভারি অর্ডারটির জিজ্ঞাসা মূল্য গণনা করার সময়ও এটি সত্য, বিক্রয় 1 দামকে আরও ওজন দেওয়া। কেন এটি 0.618, এটি হতে পারে যে ডেভেলপার সোনার অনুপাত পছন্দ করে। শেষের দিকে দামের সামান্য অংশ (0.01) যোগ বা বিয়োগ করার জন্য, এটি বাজারের কেন্দ্রে আরও কিছুটা কমিয়ে আনতে হবে।
সময় সিরিজের অর্ডার বইয়ের প্রথম তিনটি স্তরের ওজনযুক্ত গড় মূল্য আপডেট করুন
অর্ডার বইয়ের প্রথম তিনটি স্তরের ক্রয় আদেশ এবং বিক্রয় আদেশের জন্য, একটি ওজনযুক্ত গড় গণনা করা হয়। প্রথম স্তরের ওজন 0.7, দ্বিতীয় স্তরের ওজন 0.2, এবং তৃতীয় স্তরের ওজন 0.1। কিছু শিক্ষার্থী বলতে পারেঃ
চলুন দেখি বিস্তারিত হিসাবঃ
(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
এখানে দেখা যাচ্ছে যে চূড়ান্ত হিসাবকৃত মূল্য প্রকৃতপক্ষে বর্তমান বাজারে তৃতীয় স্তরের মধ্যম মূল্যের অবস্থানকে প্রতিফলিত করে।
তারপর এই হিসাবকৃত মূল্য ব্যবহার করুন আপডেট করার জন্যself.prices
অ্যারে, পুরাতন তথ্য (দ্বারাshift()
(উপলব্ধ) এবং সর্বশেষতম তথ্যের সাথে এটি আপডেট করুনpush()
ফাংশন; self.prices
সময় সিরিজের ক্রম অনুযায়ী একটি তথ্য প্রবাহ।
একটু বিশ্রাম নাও, বিশ্লেষণ শেষ কর, পরের বার দেখা হবে!