पिछले लेख मेंएफएमजेड प्लेटफॉर्म के बाहरी सिग्नल रिसेप्शन पर चर्चाः विस्तारित एपीआई बनाम रणनीति अंतर्निहित HTTP सेवा, हमने प्रोग्रामेटिक ट्रेडिंग के लिए बाहरी संकेत प्राप्त करने के दो अलग-अलग तरीकों की तुलना की और विवरण का विश्लेषण किया। बाहरी संकेत प्राप्त करने के लिए एफएमजेड प्लेटफॉर्म विस्तारित एपीआई का उपयोग करने का समाधान प्लेटफॉर्म रणनीति पुस्तकालय में एक पूरी रणनीति है। इस लेख में, चलो संकेत प्राप्त करने के लिए रणनीति अंतर्निहित एचटीपी सेवा का उपयोग करने का एक पूरा समाधान लागू करते हैं।
ट्रेडिंग व्यू सिग्नल तक पहुँचने के लिए एफएमजेड एक्सटेंशन एपीआई का उपयोग करने की पिछली रणनीति के बाद, हम पिछले संदेश प्रारूप, संदेश प्रसंस्करण विधि, आदि का उपयोग करते हैं और रणनीति में सरल संशोधन करते हैं।
क्योंकि रणनीति में निर्मित सेवाएं Http या HTTPS का उपयोग कर सकती हैं, एक सरल प्रदर्शन के लिए, हम Http प्रोटोकॉल का उपयोग करते हैं, आईपी व्हाइटलिस्ट सत्यापन जोड़ते हैं, और पासवर्ड सत्यापन जोड़ते हैं। यदि सुरक्षा को और बढ़ाने की आवश्यकता है, तो रणनीति में निर्मित सेवा को एक Https सेवा के रूप में डिज़ाइन किया जा सकता है।
//Signal structure
var Template = {
Flag: "45M103Buy", // Logo, can be specified at will
Exchange: 1, // Designated exchange trading pairs
Currency: "BTC_USDT", // Trading pairs
ContractType: "spot", // Contract type, swap, quarter, next_quarter, spot fill in spot
Price: "{{close}}", // Opening or closing price, -1 is the market price
Action: "buy", // Transaction type [buy: spot buy, sell: spot sell, long: futures long, short: futures short, closesell: futures buy to close short, closebuy: futures sell to close long]
Amount: "1", // Trading volume
}
var Success = "#5cb85c" // Success color
var Danger = "#ff0000" // Danger color
var Warning = "#f0ad4e" // Warning color
var buffSignal = []
// Http service
function serverFunc(ctx, ipWhiteList, passPhrase) {
var path = ctx.path()
if (path == "/CommandRobot") {
// Verify IP address
var fromIP = ctx.remoteAddr().split(":")[0]
if (ipWhiteList && ipWhiteList.length > 0) {
var ipList = ipWhiteList.split(",")
if (!ipList.includes(fromIP)) {
ctx.setStatus(500)
ctx.write("IP address not in white list")
Log("500 Error: IP address not in white list", "#FF0000")
return
}
}
// Verify password
var pass = ctx.rawQuery().length > 0 ? ctx.query("passPhrase") : ""
if (passPhrase && passPhrase.length > 0) {
if (pass != passPhrase) {
ctx.setStatus(500)
ctx.write("Authentication failed")
Log("500 Error: Authentication failed", "#FF0000")
return
}
}
var body = JSON.parse(ctx.body())
threading.mainThread().postMessage(JSON.stringify(body))
ctx.write("OK")
// 200
} else {
ctx.setStatus(404)
}
}
// Check signal message format
function DiffObject(object1, object2) {
const keys1 = Object.keys(object1)
const keys2 = Object.keys(object2)
if (keys1.length !== keys2.length) {
return false
}
for (let i = 0; i < keys1.length; i++) {
if (keys1[i] !== keys2[i]) {
return false
}
}
return true
}
function CheckSignal(Signal) {
Signal.Price = parseFloat(Signal.Price)
Signal.Amount = parseFloat(Signal.Amount)
if (Signal.Exchange <= 0 || !Number.isInteger(Signal.Exchange)) {
Log("The minimum exchange number is 1 and is an integer.", Danger)
return
}
if (Signal.Amount <= 0 || typeof(Signal.Amount) != "number") {
Log("The trading volume cannot be less than 0 and must be a numeric type.", typeof(Signal.Amount), Danger)
return
}
if (typeof(Signal.Price) != "number") {
Log("Price must be a numeric value", Danger)
return
}
if (Signal.ContractType == "spot" && Signal.Action != "buy" && Signal.Action != "sell") {
Log("The instruction is to operate spot goods, and the Action is wrong, Action:", Signal.Action, Danger)
return
}
if (Signal.ContractType != "spot" && Signal.Action != "long" && Signal.Action != "short" && Signal.Action != "closesell" && Signal.Action != "closebuy") {
Log("The instruction is to operate futures, and the Action is wrong, Action:", Signal.Action, Danger)
return
}
return true
}
// Signal processing object
function createManager() {
var self = {}
self.tasks = []
self.process = function() {
var processed = 0
if (self.tasks.length > 0) {
_.each(self.tasks, function(task) {
if (!task.finished) {
processed++
self.pollTask(task)
}
})
if (processed == 0) {
self.tasks = []
}
}
}
self.newTask = function(signal) {
// {"Flag":"45M103Buy","Exchange":1,"Currency":"BTC_USDT","ContractType":"swap","Price":"10000","Action":"buy","Amount":"0"}
var task = {}
task.Flag = signal["Flag"]
task.Exchange = signal["Exchange"]
task.Currency = signal["Currency"]
task.ContractType = signal["ContractType"]
task.Price = signal["Price"]
task.Action = signal["Action"]
task.Amount = signal["Amount"]
task.exchangeIdx = signal["Exchange"] - 1
task.pricePrecision = null
task.amountPrecision = null
task.error = null
task.exchangeLabel = exchanges[task.exchangeIdx].GetLabel()
task.finished = false
Log("Create a task:", task)
self.tasks.push(task)
}
self.getPrecision = function(n) {
var precision = null
var arr = n.toString().split(".")
if (arr.length == 1) {
precision = 0
} else if (arr.length == 2) {
precision = arr[1].length
}
return precision
}
self.pollTask = function(task) {
var e = exchanges[task.exchangeIdx]
var name = e.GetName()
var isFutures = true
e.SetCurrency(task.Currency)
if (task.ContractType != "spot" && name.indexOf("Futures_") != -1) {
// If it is not spot, set up a contract
e.SetContractType(task.ContractType)
} else if (task.ContractType == "spot" && name.indexOf("Futures_") == -1) {
isFutures = false
} else {
task.error = "The ContractType in the instruction does not match the configured exchange object type"
return
}
var depth = e.GetDepth()
if (!depth || !depth.Bids || !depth.Asks) {
task.error = "Abnormal order book data"
return
}
if (depth.Bids.length == 0 && depth.Asks.length == 0) {
task.error = "No orders on the market"
return
}
_.each([depth.Bids, depth.Asks], function(arr) {
_.each(arr, function(order) {
var pricePrecision = self.getPrecision(order.Price)
var amountPrecision = self.getPrecision(order.Amount)
if (Number.isInteger(pricePrecision) && !Number.isInteger(self.pricePrecision)) {
self.pricePrecision = pricePrecision
} else if (Number.isInteger(self.pricePrecision) && Number.isInteger(pricePrecision) && pricePrecision > self.pricePrecision) {
self.pricePrecision = pricePrecision
}
if (Number.isInteger(amountPrecision) && !Number.isInteger(self.amountPrecision)) {
self.amountPrecision = amountPrecision
} else if (Number.isInteger(self.amountPrecision) && Number.isInteger(amountPrecision) && amountPrecision > self.amountPrecision) {
self.amountPrecision = amountPrecision
}
})
})
if (!Number.isInteger(self.pricePrecision) || !Number.isInteger(self.amountPrecision)) {
task.err = "Failed to get precision"
return
}
e.SetPrecision(self.pricePrecision, self.amountPrecision)
// buy: spot purchase, sell: spot sell, long: futures long, short: futures short, closesell: futures buy to close short, closebuy: futures sell to close long
var direction = null
var tradeFunc = null
if (isFutures) {
switch (task.Action) {
case "long":
direction = "buy"
tradeFunc = e.Buy
break
case "short":
direction = "sell"
tradeFunc = e.Sell
break
case "closesell":
direction = "closesell"
tradeFunc = e.Buy
break
case "closebuy":
direction = "closebuy"
tradeFunc = e.Sell
break
}
if (!direction || !tradeFunc) {
task.error = "Wrong transaction direction:" + task.Action
return
}
e.SetDirection(direction)
} else {
if (task.Action == "buy") {
tradeFunc = e.Buy
} else if (task.Action == "sell") {
tradeFunc = e.Sell
} else {
task.error = "Wrong transaction direction:" + task.Action
return
}
}
var id = tradeFunc(task.Price, task.Amount)
if (!id) {
task.error = "Order failed"
}
task.finished = true
}
return self
}
function main() {
// Reset log information
if (isResetLog) {
LogReset(1)
}
Log("Transaction type [buy: spot buy, sell: spot sell, long: futures long, short: futures short, closesell: futures buy to close short, closebuy: futures sell to close long]", Danger)
Log("Instruction templates:", JSON.stringify(Template), Danger)
if (!passPhrase || passPhrase.length == 0) {
Log("webhook url:", `http://${serverIP}:${port}/CommandRobot`)
} else {
Log("webhook url:", `http://${serverIP}:${port}/CommandRobot?passPhrase=${passPhrase}`)
}
// Creating an Http built-in service
__Serve("http://0.0.0.0:" + port, serverFunc, ipWhiteList, passPhrase)
// Initialize the code to execute
if (initCode && initCode.length > 0) {
try {
Log("Execute the initialization code:", initCode)
eval(initCode)
} catch(error) {
Log("e.name:", error.name, "e.stack:", error.stack, "e.message:", error.message)
}
}
// Create a signal management object
var manager = createManager()
while (true) {
try {
// Detect interactive controls for testing
var cmd = GetCommand()
if (cmd) {
// Send Http request, simulate test
var arrCmd = cmd.split(":", 2)
if (arrCmd[0] == "TestSignal") {
// {"Flag":"TestSignal","Exchange":1,"Currency":"BTC_USDT","ContractType":"swap","Price":"10000","Action":"long","Amount":"1"}
var signal = cmd.replace("TestSignal:", "")
if (!passPhrase || passPhrase.length == 0) {
var ret = HttpQuery(`http://${serverIP}:${port}/CommandRobot`, {"method": "POST", "body": JSON.stringify(signal)})
Log("Test request response:", ret)
} else {
var ret = HttpQuery(`http://${serverIP}:${port}/CommandRobot?passPhrase=${passPhrase}`, {"method": "POST", "body": JSON.stringify(signal)})
Log("Test request response:", ret)
}
}
}
// Detect the message that the built-in Http service notifies the main thread after receiving the request, and writes it to the task queue of the manager object
var msg = threading.mainThread().peekMessage(-1)
if (msg) {
Log("Receive message msg:", msg)
var objSignal = JSON.parse(msg)
if (DiffObject(Template, objSignal)) {
Log("Receive trading signal instructions:", objSignal)
buffSignal.push(objSignal)
// Check trading volume, exchange ID
if (!CheckSignal(objSignal)) {
continue
}
// Create a task
if (objSignal["Flag"] == "TestSignal") {
Log("Received test message:", JSON.stringify(objSignal))
} else {
manager.newTask(objSignal)
}
} else {
Log("Command not recognized", signal)
}
} else {
Sleep(1000 * SleepInterval)
}
// Processing tasks
manager.process()
// Status bar displays signal
if (buffSignal.length > maxBuffSignalRowDisplay) {
buffSignal.shift()
}
var buffSignalTbl = {
"type" : "table",
"title" : "Signal recording",
"cols" : ["Flag", "Exchange", "Currency", "ContractType", "Price", "Action", "Amount"],
"rows" : []
}
for (var i = buffSignal.length - 1 ; i >= 0 ; i--) {
buffSignalTbl.rows.push([buffSignal[i].Flag, buffSignal[i].Exchange, buffSignal[i].Currency, buffSignal[i].ContractType, buffSignal[i].Price, buffSignal[i].Action, buffSignal[i].Amount])
}
LogStatus(_D(), "\n", "`" + JSON.stringify(buffSignalTbl) + "`")
} catch (error) {
Log("e.name:", error.name, "e.stack:", error.stack, "e.message:", error.message)
}
}
}
बाहरी संकेतों तक पहुँचने के लिए विस्तारित एपीआई का उपयोग करने की रणनीति की तुलना में, रणनीति बहुत अधिक नहीं बदलती है। यह केवल एक अतिरिक्त एपीआई जोड़ती है।serverFunc
एचटीटीपी सेवा प्रसंस्करण कार्य करता है और एफएमजेड प्लेटफॉर्म द्वारा हाल ही में जोड़ा गया मल्टी-थ्रेडेड संदेश पासिंग विधि का उपयोग करता हैःpostMessage
/ peekMessage
अन्य कोड लगभग अपरिवर्तित हैं।
चूंकि ट्रेडिंग व्यू
52.89.214.238
34.212.75.30
54.218.53.128
52.32.178.7
इसलिए, हम एक पैरामीटर जोड़ते हैंipWhiteList
आईपी पते की श्वेतसूची सेट करने के लिए रणनीति के लिए. सभी अनुरोध है कि आईपी पते की श्वेतसूची में नहीं हैं अनदेखा किया जाएगा.
// Verify IP address
var fromIP = ctx.remoteAddr().split(":")[0]
if (ipWhiteList && ipWhiteList.length > 0) {
var ipList = ipWhiteList.split(",")
if (!ipList.includes(fromIP)) {
ctx.setStatus(500)
ctx.write("IP address not in white list")
Log("500 Error: IP address not in white list", "#FF0000")
return
}
}
एक पैरामीटर जोड़ेंpassPhrase
सत्यापन पासवर्ड सेट करने के लिए रणनीति के लिए. यह पासवर्ड ट्रेडिंग दृश्य पर Webhook url सेटिंग्स में कॉन्फ़िगर किया गया है. सत्यापन पासवर्ड से मेल नहीं खाते अनुरोधों को अनदेखा कर दिया जाएगा.
उदाहरण के लिए, हम सेट करते हैंःtest123456
.
// Verify password
var pass = ctx.rawQuery().length > 0 ? ctx.query("passPhrase") : ""
if (passPhrase && passPhrase.length > 0) {
if (pass != passPhrase) {
ctx.setStatus(500)
ctx.write("Authentication failed")
Log("500 Error: Authentication failed", "#FF0000")
return
}
}
बाहरी सिग्नल ट्रिगर स्रोत के रूप में ट्रेडिंग व्यू प्लेटफॉर्म की PINE स्क्रिप्ट का उपयोग करें, और ट्रेडिंग व्यू द्वारा आधिकारिक रूप से जारी किए गए यादृच्छिक रूप से PINE स्क्रिप्ट में से एक का चयन करेंः
//@version=6
strategy("MovingAvg Cross", overlay=true)
length = input(9)
confirmBars = input(1)
price = close
ma = ta.sma(price, length)
bcond = price > ma
bcount = 0
bcount := bcond ? nz(bcount[1]) + 1 : 0
if (bcount == confirmBars)
strategy.entry("MACrossLE", strategy.long, comment="long")
scond = price < ma
scount = 0
scount := scond ? nz(scount[1]) + 1 : 0
if (scount == confirmBars)
strategy.entry("MACrossSE", strategy.short, comment="short")
बेशक, आप लाइव ट्रेडिंग निष्पादित करने के लिए सीधे FMZ प्लेटफॉर्म पर PINE स्क्रिप्ट भी चला सकते हैं, लेकिन यदि आप चाहते हैं कि ट्रेडिंग व्यू प्लेटफॉर्म सिग्नल भेजने के लिए PINE स्क्रिप्ट चलाए, तो आप केवल हमारे द्वारा चर्चा किए गए समाधानों का उपयोग कर सकते हैं।
हम इस स्क्रिप्ट के आदेश प्लेसिंग समारोह पर ध्यान केंद्रित करने की जरूरत है. हमारे webhook अनुरोध में संदेश के लिए इस पाइन स्क्रिप्ट अनुकूल करने के लिए, हम व्यापार समारोह को संशोधित करने की जरूरत हैcomment
, जिसका उल्लेख हम आगे लेख में करेंगे।
WebhookUrl और अनुरोध शरीर की सेटिंग्स मूल रूप से बाहरी संकेतों तक पहुँचने के लिए पिछली विस्तारित एपीआई विधि के समान हैं। एक ही भागों को इस लेख में दोहराया नहीं जाएगा। आप पिछले लेख का संदर्भ ले सकते हैं।
ट्रेडिंग व्यू पर एक बाजार के चार्ट में इस पाइन स्क्रिप्ट को जोड़ने के बाद, हम देख सकते हैं कि स्क्रिप्ट ने काम करना शुरू कर दिया है। फिर हम स्क्रीनशॉट में दिखाए गए अनुसार स्क्रिप्ट में एक अलर्ट जोड़ते हैं।
वेबहूक URL सेटिंग्सः स्ट्रैटे कोड को स्वचालित रूप से वेबहुक यूआरएल उत्पन्न करने के लिए डिज़ाइन किया गया है. हमें केवल रणनीति ऑपरेशन की शुरुआत में लॉग से इसकी प्रतिलिपि बनाने की आवश्यकता है.
http://xxx.xxx.xxx.xxx:80/CommandRobot?passPhrase=test123456
ट्रेडिंग व्यू यह निर्धारित करता है कि Webhook URL केवल HTTP अनुरोधों के लिए पोर्ट 80 का उपयोग कर सकता है, इसलिए हम भी रणनीति में पोर्ट पैरामीटर 80 पर सेट करते हैं, इसलिए हम देख सकते हैं कि रणनीति द्वारा उत्पन्न Webhook URL का लिंक पोर्ट भी 80 है।
फिर हम
{
"Flag":"{{strategy.order.id}}",
"Exchange":1,
"Currency":"ETH_USDT",
"ContractType":"swap",
"Price":"-1",
"Action":"{{strategy.order.comment}}",
"Amount":"{{strategy.order.contracts}}"
}
क्या आपको याद है आदेश प्लेसमेंट कोड में पाइन स्क्रिप्ट हम सिर्फ के बारे में बात की थी? चलो
strategy.entry("MACrossLE", strategy.long, comment="long")
तो सेटिंग्स सुसंगत होना चाहिए. यहाँ हम सेट
PINE स्क्रिप्ट प्रत्येक ऑर्डर के लिए ऑर्डर मात्रा निर्दिष्ट नहीं करती है, इसलिए जब ट्रेडिंग व्यू एक अलर्ट संदेश भेजता है, तो यह
जब ट्रेडिंग व्यू पर चल रही पाइन स्क्रिप्ट ट्रेडिंग फ़ंक्शन निष्पादित करती है, क्योंकि हमने वेबहूक यूआरएल अलर्ट सेट किया है, ट्रेडिंग व्यू प्लेटफॉर्म हमारी रणनीति की अंतर्निहित एचटीपी सेवा को एक पोस्ट अनुरोध भेजेगा। इस अनुरोध क्वेरी में एक पासवर्ड पैरामीटर होता हैpassPhrase
सत्यापन के लिए प्राप्त वास्तविक अनुरोध निकाय इस प्रकार है:
फिर हमारी रणनीति इस शरीर में संदेश के आधार पर संबंधित व्यापारिक संचालन निष्पादित करती है।
यह देखा जा सकता है कि रणनीति ट्रेडिंग व्यू पर PINE स्क्रिप्ट के अनुसार OKX सिमुलेशन वातावरण में सिंक्रनाइज़ सिग्नल ट्रेडिंग करती है।
एफएमजेड क्वांट पर ध्यान देने के लिए धन्यवाद, और पढ़ने के लिए धन्यवाद।