संसाधन लोड हो रहा है... लोड करना...

एफएमजेड प्लेटफॉर्म के बाहरी सिग्नल रिसेप्शन पर चर्चाः रणनीति में अंतर्निहित एचटीपी सेवा के साथ सिग्नल प्राप्त करने के लिए एक पूर्ण समाधान

लेखक:FMZ~Lydia, बनाया गयाः 2024-12-18 09:22:33, अद्यतनः 2024-12-18 09:24:49

प्रस्तावना

पिछले लेख मेंएफएमजेड प्लेटफॉर्म के बाहरी सिग्नल रिसेप्शन पर चर्चाः विस्तारित एपीआई बनाम रणनीति अंतर्निहित 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)
        }        
    }
}

img

  • पोर्ट पैरामीटर: यदि आप HTTP प्रोटोकॉल का उपयोग करते हैं, तो आप केवल पोर्ट 80 को ट्रेडिंग व्यू पर सेट कर सकते हैं।
  • serverIP पैरामीटर: सर्वर का सार्वजनिक IP पता दर्ज करें.
  • initCode पैरामीटर: इसका उपयोग एक्सचेंज परीक्षण वातावरण में परीक्षण के लिए आधार पते को स्विच करने के लिए किया जा सकता है।

बाहरी संकेतों तक पहुँचने के लिए विस्तारित एपीआई का उपयोग करने की रणनीति की तुलना में, रणनीति बहुत अधिक नहीं बदलती है। यह केवल एक अतिरिक्त एपीआई जोड़ती है।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 और अनुरोध निकाय सेटिंग्स

WebhookUrl और अनुरोध शरीर की सेटिंग्स मूल रूप से बाहरी संकेतों तक पहुँचने के लिए पिछली विस्तारित एपीआई विधि के समान हैं। एक ही भागों को इस लेख में दोहराया नहीं जाएगा। आप पिछले लेख का संदर्भ ले सकते हैं।

वेबहूक यूआरएल

img

ट्रेडिंग व्यू पर एक बाजार के चार्ट में इस पाइन स्क्रिप्ट को जोड़ने के बाद, हम देख सकते हैं कि स्क्रिप्ट ने काम करना शुरू कर दिया है। फिर हम स्क्रीनशॉट में दिखाए गए अनुसार स्क्रिप्ट में एक अलर्ट जोड़ते हैं।

img

वेबहूक URL सेटिंग्सः स्ट्रैटे कोड को स्वचालित रूप से वेबहुक यूआरएल उत्पन्न करने के लिए डिज़ाइन किया गया है. हमें केवल रणनीति ऑपरेशन की शुरुआत में लॉग से इसकी प्रतिलिपि बनाने की आवश्यकता है.

img

http://xxx.xxx.xxx.xxx:80/CommandRobot?passPhrase=test123456

ट्रेडिंग व्यू यह निर्धारित करता है कि Webhook URL केवल HTTP अनुरोधों के लिए पोर्ट 80 का उपयोग कर सकता है, इसलिए हम भी रणनीति में पोर्ट पैरामीटर 80 पर सेट करते हैं, इसलिए हम देख सकते हैं कि रणनीति द्वारा उत्पन्न Webhook URL का लिंक पोर्ट भी 80 है।

शरीर संदेश

img

फिर हम Settings टैब में अनुरोध शरीर संदेश सेट स्क्रीनशॉट में दिखाया गया है के रूप में.

{
    "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")

MACrossLE में भरा हुआ सामग्री है {{strategy.order.id}} जब भविष्य में अलर्ट ट्रिगर किया जाता है.

long {{strategy.order.comment}} में भरी गई सामग्री है जब भविष्य में अलर्ट ट्रिगर किया जाता है। रणनीति में पहचाना गया संकेत है (नीचे स्क्रीनशॉट):

img

तो सेटिंग्स सुसंगत होना चाहिए. यहाँ हम सेट long और short आदेश समारोह के लिए, संकेत खुला लंबा और खुला छोटा संकेत का संकेत.

PINE स्क्रिप्ट प्रत्येक ऑर्डर के लिए ऑर्डर मात्रा निर्दिष्ट नहीं करती है, इसलिए जब ट्रेडिंग व्यू एक अलर्ट संदेश भेजता है, तो यह {{strategy.order.contracts}} भाग को भरने के लिए डिफ़ॉल्ट ऑर्डर मात्रा का उपयोग करता है।

लाइव ट्रेडिंग टेस्ट

img

img

जब ट्रेडिंग व्यू पर चल रही पाइन स्क्रिप्ट ट्रेडिंग फ़ंक्शन निष्पादित करती है, क्योंकि हमने वेबहूक यूआरएल अलर्ट सेट किया है, ट्रेडिंग व्यू प्लेटफॉर्म हमारी रणनीति की अंतर्निहित एचटीपी सेवा को एक पोस्ट अनुरोध भेजेगा। इस अनुरोध क्वेरी में एक पासवर्ड पैरामीटर होता हैpassPhraseसत्यापन के लिए प्राप्त वास्तविक अनुरोध निकाय इस प्रकार है:

img

फिर हमारी रणनीति इस शरीर में संदेश के आधार पर संबंधित व्यापारिक संचालन निष्पादित करती है।

यह देखा जा सकता है कि रणनीति ट्रेडिंग व्यू पर PINE स्क्रिप्ट के अनुसार OKX सिमुलेशन वातावरण में सिंक्रनाइज़ सिग्नल ट्रेडिंग करती है।

रणनीतिक पता

https://www.fmz.com/strategy/475235

एफएमजेड क्वांट पर ध्यान देने के लिए धन्यवाद, और पढ़ने के लिए धन्यवाद।


अधिक