हाल ही में इस विषय पर एक गर्म चर्चा हुई है।print money
एफएमजेड क्वांट वीचैट समूह में रोबोट। एक बहुत पुरानी रणनीति क्वांट की आंखों में फिर से प्रवेश कर गई हैः लीक्सरिपर।
रोबोट ट्रेडिंग का सिद्धांतprint money
मैं अपने आप को दोष देता हूं कि उस समय मैंने लीक्सरीपर रणनीति में बहुत अधिक नहीं पढ़ा और इसे बहुत अच्छी तरह से नहीं समझा। इसलिए, मैंने मूल रणनीति को फिर से ध्यान से पढ़ा और एफएमजेड क्वांट पर प्रत्यारोपित ओकेकोइन लीक्सरीपर के प्रत्यारोपित संस्करण को देखा।
एफएमजेड क्वांट प्लेटफॉर्म पर आधारित प्रत्यारोपित लीजरीपर की रणनीति का विश्लेषण किया जाता है ताकि प्लेटफॉर्म उपयोगकर्ता इस रणनीति के विचार को सीख सकें।
इस लेख में, हम प्रोग्रामिंग से संबंधित उबाऊ सामग्री को कम करने के लिए रणनीति विचार और इरादे के पहलुओं से अधिक विश्लेषण करेंगे।
[ओकेकोइन लीक्सरीपर प्रत्यारोपण] रणनीति का स्रोत कोडः
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 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 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 रणनीति तर्क मॉड्यूल (एक वस्तु) के निर्माता के रूप में समझा जा सकता है। संक्षेप में,LeeksReaper()
एक पोरसीपर ट्रेडिंग लॉजिक के निर्माण के लिए जिम्मेदार है।
कीवर्डः
· रणनीति की पहली पंक्तिmain
कार्य:var reaper = LeeksReaper()
, कोड एक स्थानीय चर घोषित करता हैreaper
और फिर एक रणनीति तर्क ऑब्जेक्ट बनाने के लिए LeeksReaper() फ़ंक्शन को कॉल करता है जो एक मान को असाइन करता हैreaper
.
रणनीति का अगला कदमmain
कार्य:
while (true) {
reaper.poll()
Sleep(TickInterval)
}
एक दर्ज करेंwhile
अंतहीन लूप और प्रसंस्करण समारोह निष्पादित रखनेpoll()
केreaper
वस्तु,poll()
कार्य ठीक है जहां व्यापार रणनीति का मुख्य तर्क झूठ है और पूरी रणनीति कार्यक्रम बार-बार व्यापार तर्क को निष्पादित करने के लिए शुरू होता है।
और लाइन के बारे मेंSleep(TickInterval)
, यह समझने में आसान है, यह व्यापार तर्क की रोटेशन आवृत्ति को नियंत्रित करने के उद्देश्य से समग्र व्यापार तर्क के प्रत्येक निष्पादन के बाद विराम समय को नियंत्रित करने के लिए है।
LeeksReaper()
निर्मातादेखो कैसेLeeksReaper()
फ़ंक्शन एक रणनीति तर्क वस्तु का निर्माण करता है।
..LeeksReaper()
फ़ंक्शन एक खाली ऑब्जेक्ट घोषित करके शुरू होता है,var self = {}
, और निष्पादन के दौरानLeeksReaper()
समारोह धीरे-धीरे इस खाली वस्तु के लिए कुछ विधियों और विशेषताओं को जोड़ देगा, अंत में इस वस्तु के निर्माण को पूरा करने और इसे वापस करने के लिए (यानी, चरण केmain()
कार्य के अंदरvar reaper = LeeksReaper()
, लौटाया वस्तु के लिए सौंपा जाता हैreaper
).
विशेषताएं जोड़ेंself
वस्तु
इसके बाद, मैं करने के लिए गुणों की एक बहुत जोड़ाself
. मैं प्रत्येक विशेषता का वर्णन इस प्रकार करूंगा, जो इन विशेषताओं और चरों के उद्देश्य और इरादे को जल्दी से समझ सकता है, रणनीतियों की समझ को आसान बना सकता है, और कोड देखते समय भ्रमित होने से बच सकता है।
self.numTick = 0 # It is used to record the number of transactions not triggered when the poll function is called. When the order is triggered and the order logic is executed, self.numTick is reset to 0
self.lastTradeId = 0 # The transaction record ID of the order that has been transacted in the transaction market. This variable records the current transaction record ID of the market
self.vol = 0 # Reference to the trading volume of each market inspection after weighted average calculation (market data is obtained once per loop, which can be interpreted as a time of market inspection)
self.askPrice = 0 # The bill of lading price of the sales order can be understood as the price of the listing order after the strategy is calculated
self.bidPrice = 0 # Purchase order bill of lading price
self.orderBook = {Asks:[], Bids:[]} # Record the currently obtained order book data, that is, depth data (sell one... sell n, buy one... buy n)
self.prices = [] # An array that records the prices on the time series after the calculation of the first three weighted averages in the order book, which means that each time the first three weighted averages of the order book are stored, they are placed in an array and used as a reference for subsequent strategy trading signals, so the variable name is prices, in plural form, indicating a set of prices
self.tradeOrderId = 0 # Record the order ID after the current bill of lading is placed
self.p = 0.5 # Position proportion: when the value of currency accounts for exactly half of the total asset value, the value is 0.5, that is, the equilibrium state
self.account = null # Record the account asset data, which is returned by the GetAccount() function
self.preCalc = 0 # Record the timestamp of the last time when the revenue was calculated, in milliseconds, to control the frequency of triggering the execution of the revenue calculation code
self.preNet = 0 # Record current return values
स्वयं के लिए इन विशेषताओं को जोड़ने के बाद, करने के लिए तरीकों को जोड़ने के लिए शुरूself
ऑब्जेक्ट ताकि यह ऑब्जेक्ट कुछ काम कर सकता है और कुछ कार्य कर सकता है।
पहला फ़ंक्शन जोड़ता हैः
self.updateTrades = function() {
var trades = _C(exchange.GetTrades) # Call the FMZ encapsulated interface GetTrades to obtain the latest market transaction data
if (self.prices.length == 0) { # When self.prices.length == 0, the self.prices array needs to be filled with numeric values, which will be triggered only when the strategy starts running
while (trades.length == 0) { # If there is no recent transaction record in the market, the while loop will keep executing until the latest transaction data is available and update the trades variable
trades = trades.concat(_C(exchange.GetTrades)) # concat is a method of JS array type, which is used to concatenate two arrays, here is to concatenate the "trades" array and the array data returned by "_C(exchange.GetTrades)" into one array
}
for (var i = 0; i < 15; i++) { # Fill in data to self.prices, and fill in 15 pieces of latest transaction prices
self.prices[i] = trades[trades.length - 1].Price
}
}
self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) { # _. Reduce function is used for iterative calculation to accumulate the amount of the latest transaction records
// 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 डेटा लिंक (https://underscorejs.net/#reduce)
इसका अर्थ भी बहुत सरल है, उदाहरण के लिएः
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
}
यानी, सरणी में प्रत्येक संख्या जोड़ने[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
. फ़ंक्शन के नाम से, हम देख सकते हैं कि इसका उपयोग ऑर्डर बुक को अपडेट करने के लिए किया जाता है. हालांकि यह केवल ऑर्डर बुक को अपडेट नहीं करता है. फ़ंक्शन FMZ API फ़ंक्शन को कॉल करना शुरू करता है.GetDepth()
वर्तमान बाजार ऑर्डर बुक डेटा (एक बेचें...एन बेचें, एक खरीदें...एन खरीदें) प्राप्त करने और ऑर्डर बुक डेटा को रिकॉर्ड करने के लिए।self.orderBook
. अगला, यदि आदेश पुस्तिका डेटा के खरीद आदेश और बिक्री आदेश 3 से कम हैं, तो यदि ऐसा है, तो अमान्य फ़ंक्शन सीधे लौटाया जाएगा।
उसके बाद दो आंकड़ों की गणना की जाती हैः
· चालान मूल्य की गणना माल ढुलाई का मूल्य भी भारित औसत विधि का उपयोग करके गणना की जाती है। खरीद आदेश की गणना करते समय, लेनदेन मूल्य के निकटतम खरीद मूल्य को 61.8% (0.618) और लेनदेन मूल्य के निकटतम बिक्री मूल्य को 38.2% (0.382) का भार दिया जाता है। माल ढुलाई बिल की बिक्री मूल्य की गणना करते समय, लेनदेन मूल्य के निकटतम बिक्री मूल्य को समान वजन दिया जाता है। जैसा कि 0.618 है, यह हो सकता है कि लेखक स्वर्ण खंड अनुपात को पसंद करता है। जैसा कि अंतिम मूल्य (0.01) के लिए है, यह उद्घाटन के केंद्र में थोड़ा ऑफसेट करना है।
समय श्रृंखला पर ऑर्डर बही के पहले तीन स्तरों के भारित औसत मूल्य को अद्यतन करें
ऑर्डर बुक में पहले तीन स्तरों के खरीद और बिक्री ऑर्डर की कीमतों के लिए, भारित औसत की गणना की जाती है। पहले स्तर का वजन 0.7 है, दूसरे स्तर का वजन 0.2 है, और तीसरे स्तर का वजन 0.1 है। कोई कह सकता है,
(Buy one+Sell one) * 0.35+(Buy two+Sell two) * 0.1+(Buy three+Sell three) * 0.05
->
(Buy one+sell one)/2 * 2 * 0.35+(Buy two+sell two)/2 * 2 * 0.1+(Buy three+sell three)/2 * 2 * 0.05
->
(Buy one+sell one)/2 * 0.7+(Buy two+sell two)/2 * 0.2+(Buy three+sell three)/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
, जो समय श्रृंखला क्रम के साथ डेटा की एक धारा है।
तो चलो यहाँ आराम करते हैं, और हम अगले अंक में आप देखेंगे ~