Tài nguyên đang được tải lên... tải...

Cuộc thảo luận về tiếp nhận tín hiệu bên ngoài của nền tảng FMZ: API mở rộng VS Chiến lược Dịch vụ HTTP tích hợp

Tác giả:FMZ~Lydia, Tạo: 2024-12-13 13:12:31, Cập nhật: 2024-12-16 11:32:35

img

Lời giới thiệu

Có một số bài viết trên nền tảng Digest về việc kết nối với các webhook Trading View, cho phép các chiến lược điều khiển giao dịch với tín hiệu từ các hệ thống bên ngoài.CommandRobot, nói đơn giản, yêu cầu http/https của tín hiệu bên ngoài được gửi đến nền tảng FMZ, và nền tảng chuyển tiếp tín hiệu như một thông điệp tương tác chiến lược đến chương trình chiến lược.

Khi nền tảng phát triển và lặp lại, nhiều tính năng mới đã được cập nhật và nâng cấp. Ngoài ra còn có các giải pháp mới để nhận tín hiệu bên ngoài. Mỗi giải pháp có những lợi thế riêng của nó. Trong bài viết này, chúng tôi sẽ thảo luận về chủ đề này cùng nhau.

Sử dụng nền tảng FMZ để mở rộng giao diện API

Ưu điểm của việc sử dụng phương pháp này để kết nối với các hệ thống bên ngoài là nó tương đối đơn giản, an toàn cao và dựa vào sự ổn định của giao diện API mở rộng của nền tảng.

Quá trình nhận tín hiệu bên ngoài:

Hệ thống bên ngoài (Trading View webhook) > Dịch vụ API mở rộng FMZ > Chiến lược giao dịch trực tiếp

  1. Hệ thống bên ngoài (Trading View webhook): Ví dụ, kịch bản PINE chạy trên Trading View có thể thiết lập báo động, và khi được kích hoạt, nó sẽ gửi yêu cầu http đến địa chỉ url webhook được thiết lập như một tín hiệu.
  2. Dịch vụ API mở rộng FMZ: Sau khi truy cập giao diện thành công, nền tảng chuyển tiếp thông tin và gửi nó đến chiến lược giao dịch trực tiếp dưới dạng một tin nhắn tương tác.
  3. Chiến lược giao dịch trực tiếp: Trong chiến lược giao dịch trực tiếp, bạn có thể thiết kế chức năng GetCommand để nghe tin nhắn tương tác và thực hiện hoạt động được chỉ định sau khi phát hiện tin nhắn.

So với việc sử dụng dịch vụ Http tích hợp để tạo một dịch vụ để nhận tín hiệu trực tiếp, có một bước bổ sung ở giữa (chuyển giao nền tảng).

Chiến lược Dịch vụ Http tích hợp

Sau khi nền tảng hỗ trợ chức năng dịch vụ Http tích hợp của ngôn ngữ JavaScript, bạn có thể tạo một dịch vụ đồng thời để nghe trực tiếp tín hiệu bên ngoài. Những lợi thế là: dịch vụ Http được tạo ra là một luồng riêng biệt và không ảnh hưởng đến logic chức năng chính. Nó có thể nghe các tin nhắn như chức năng GetCommand và nghe các tín hiệu bên ngoài trực tiếp. So với việc sử dụng giải pháp API mở rộng, nó loại bỏ nhu cầu vận chuyển.

Quá trình nhận tín hiệu bên ngoài:

Hệ thống ngoài (Trading View webhook) > Chiến lược giao dịch trực tiếp

  1. Hệ thống bên ngoài (Trading View webhook): Ví dụ, kịch bản PINE chạy trên Trading View có thể thiết lập báo động, sẽ gửi yêu cầu http đến địa chỉ url webhook được thiết lập như một tín hiệu khi được kích hoạt.
  2. Chiến lược giao dịch trực tiếp: Chiến lược chạy một dịch vụ HTTP đồng thời để nhận tín hiệu bên ngoài trực tiếp.

Giải pháp này tiết kiệm một bước, nhưng để cải thiện bảo mật, tốt hơn là cấu hình dịch vụ https, đòi hỏi một số nỗ lực.

Mã thử nghiệm

Kiểm tra hai giải pháp. Chiến lược sau đây sẽ gửi 10 yêu cầu Http/Https song song trong mỗi chu kỳ để mô phỏng tín hiệu bên ngoài. Sau đó chiến lược theo dõi thông điệp tương tácthông điệp được đẩy bởi các luồng dịch vụ Http. Sau đó chương trình chiến lược so sánh các tin nhắn tín hiệu bên ngoài và tín hiệu nhận một một, phát hiện liệu có mất tín hiệu hay không, và tính toán thời gian tiêu thụ.

var httpUrl = "http://123.123.123.123:8088/CommandRobot"
var accessKey = ""
var secretKey = ""

function serverFunc(ctx) {
    var path = ctx.path()
    if (path == "/CommandRobot") {
        var body = ctx.body()
        threading.mainThread().postMessage(body)
        ctx.write("OK")
        // 200
    } else {
        ctx.setStatus(404)
    }
}

function createMsgTester(accessKey, secretKey, httpUrl) {
    var tester = {}
    
    tester.currentRobotId = _G()
    tester.arrSendMsgByAPI = []
    tester.arrSendMsgByHttp = []
    tester.arrEchoMsgByAPI = []
    tester.arrEchoMsgByHttp = []
    tester.idByAPI = 0
    tester.idByHttp = 0

    var sendMsgByAPI = function(msgByAPI, robotId, accessKey, secretKey) {
        var headers = {
            "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
            "Content-Type": "application/json"
        }
        HttpQuery(`https://www.fmz.com/api/v1?access_key=${accessKey}&secret_key=${secretKey}&method=CommandRobot&args=[${robotId},+""]`, {"method": "POST", "body": JSON.stringify(msgByAPI), "headers": headers})
    }

    var sendMsgByHttp = function(msgByHttp, httpUrl) {
        HttpQuery(httpUrl, {"method": "POST", "body": JSON.stringify(msgByHttp)})
    }

    tester.run = function() {
        var robotId = tester.currentRobotId

        for (var i = 0; i < 10; i++) {
            var msgByAPI = {"ts": new Date().getTime(), "id": tester.idByAPI, "way": "ByAPI"}            
            tester.arrSendMsgByAPI.push(msgByAPI)
            tester.idByAPI++
            threading.Thread(sendMsgByAPI, msgByAPI, robotId, accessKey, secretKey)

            var msgByHttp = {"ts": new Date().getTime(), "id": tester.idByHttp, "way": "ByHttp"}
            tester.arrSendMsgByHttp.push(msgByHttp)
            tester.idByHttp++
            threading.Thread(sendMsgByHttp, msgByHttp, httpUrl)
        }
    }

    tester.getEcho =function(msg) {
        if (msg["way"] == "ByAPI") {
            tester.arrEchoMsgByAPI.push(msg)
        } else {
            tester.arrEchoMsgByHttp.push(msg)
        }
    }

    tester.deal = function() {
        var tbls = []
        for (var pair of [[tester.arrEchoMsgByHttp, tester.arrSendMsgByHttp, "ByHttp"], [tester.arrEchoMsgByAPI, tester.arrSendMsgByAPI, "ByAPI"]]) {
            var receivedMessages = pair[0]
            var sentMessages = pair[1]
            var testType = pair[2]

            var receivedMap = new Map()
            receivedMessages.forEach(message => {
                receivedMap.set(message["id"], message)
            })
            
            var matchedPairs = []
            var timeDifferences = []
            for (var sentMessage of sentMessages) {
                var receivedMessage = receivedMap.get(sentMessage["id"])
                if (receivedMessage) {
                    matchedPairs.push([JSON.stringify(sentMessage), JSON.stringify(receivedMessage), receivedMessage["ts"] - sentMessage["ts"]])
                    timeDifferences.push(receivedMessage["ts"] - sentMessage["ts"])
                } else {
                    Log("no matched sentMessage:", sentMessage, "#FF0000")
                }
            }
            
            var averageTimeDifference = timeDifferences.reduce((sum, diff) => sum + diff, 0) / timeDifferences.length
            
            var tbl = {
                "type": "table",
                "title": testType + " / averageTimeDifference:" + averageTimeDifference,
                "cols": ["send", "received", "ts diff"],
                "rows": []
            }

            for (var pair of matchedPairs) {
                tbl["rows"].push(pair)
            }

            tbls.push(tbl)
            Log(testType, ", averageTimeDifference:", averageTimeDifference, "ms")
        }

        tester.arrSendMsgByAPI = []
        tester.arrSendMsgByHttp = []
        tester.arrEchoMsgByAPI = []
        tester.arrEchoMsgByHttp = []

        return tbls
    }

    return tester
}

function main() {
    __Serve("http://0.0.0.0:8088", serverFunc)

    var t = createMsgTester(accessKey, secretKey, httpUrl)
    while (true) {
        Log("Test start...", "#FF0000")
        t.run()

        var beginTS = new Date().getTime()
        while (new Date().getTime() - beginTS < 60 * 1000) {
            var cmd = GetCommand()
            if (cmd) {
                try {
                    var obj = JSON.parse(cmd)
                    obj["ts"] = new Date().getTime()
                    t.getEcho(obj)
                } catch (e) {
                    Log(e)
                }
            }
            
            var msg = threading.mainThread().peekMessage(-1)
            if (msg) {
                try {
                    var obj = JSON.parse(msg)
                    obj["ts"] = new Date().getTime()
                    t.getEcho(obj)                
                } catch (e) {
                    Log(e)
                }
            }
        }
        Log("Waiting is over...", "#FF0000")
                
        var tbls = t.deal()
        LogStatus(_D(), "\n`" + JSON.stringify(tbls) + "`")
        Sleep(20000)
    }
}

Nếu thử nghiệm, bạn cần điền vào địa chỉ IP máy chủ cụ thể và API mở rộng của nền tảng FMZ.

var httpUrl = "http://123.123.123.123:8088/CommandRobot"
var accessKey = "xxx"
var secretKey = "xxx"
  1. Chức năng serverFunc tạo ra một dịch vụ HTTP đồng thời để theo dõi tín hiệu bên ngoài. Đối với các tin nhắn bên ngoài được nhận bởi giao diện API mở rộng, chức năng GetCommand được sử dụng để theo dõi.
  • Tin nhắn được đẩy bởi chủ đề dịch vụ HTTP: được theo dõi bởivar msg = threading.mainThread().peekMessage(-1).

  • Thông báo tương tác được chuyển tiếp bởi giao diện API mở rộng: được theo dõi bởivar cmd = GetCommand().

  1. Các quá trình gửi và nhận tín hiệu là không chặn. Nền tảng tối ưu hóa cơ chế khôi phục tài nguyên đa luồng cơ bản.Threadhoặcexchange.GoHệ thống cơ bản sẽ xử lý khôi phục tài nguyên tự động (yêu cầu phiên bản mới nhất của dock để hỗ trợ điều này).
    // Extract code snippet, send signal
    tester.run = function() {
        var robotId = tester.currentRobotId

        for (var i = 0; i < 10; i++) {
            var msgByAPI = {"ts": new Date().getTime(), "id": tester.idByAPI, "way": "ByAPI"}            
            tester.arrSendMsgByAPI.push(msgByAPI)
            tester.idByAPI++
            threading.Thread(sendMsgByAPI, msgByAPI, robotId, accessKey, secretKey)   // Concurrent calls, non-blocking

            var msgByHttp = {"ts": new Date().getTime(), "id": tester.idByHttp, "way": "ByHttp"}
            tester.arrSendMsgByHttp.push(msgByHttp)
            tester.idByHttp++
            threading.Thread(sendMsgByHttp, msgByHttp, httpUrl)                       // Concurrent calls, non-blocking
        }
    }

    // Extract code snippet, receiving signal
    var cmd = GetCommand()                              // Listen for messages from the extension API, non-blocking
    var msg = threading.mainThread().peekMessage(-1)    // Listen for messages from self-built Http service, using parameter -1, non-blocking

Tiếp theo, hãy xem xét quy trình kiểm tra, nơi thông tin được chú thích trực tiếp trong mã:

function main() {
    __Serve("http://0.0.0.0:8088", serverFunc)      // Create a concurrent http service in the current strategy instance

    var t = createMsgTester(accessKey, secretKey, httpUrl)   // Create an object for test management
    while (true) {                                           // The strategy main loop starts
        Log("Test start...", "#FF0000")
        t.run()                                              // At the beginning of each loop, the run function of the test management object is called in two ways (1. Sending signals through the extended API, 2. Sending signals directly to the Http service created by the current strategy), each way sends 10 requests concurrently

        var beginTS = new Date().getTime()
        while (new Date().getTime() - beginTS < 60 * 1000) {   // Loop detection of interactive messages from extended APIs and messages from self-built Http services
            var cmd = GetCommand()
            if (cmd) {
                try {
                    var obj = JSON.parse(cmd)
                    obj["ts"] = new Date().getTime()        // Detect interactive messages, record messages, and update time to the time received
                    t.getEcho(obj)                          // Record to the corresponding array
                } catch (e) {
                    Log(e)
                }
            }
            
            var msg = threading.mainThread().peekMessage(-1)
            if (msg) {
                try {
                    var obj = JSON.parse(msg)
                    obj["ts"] = new Date().getTime()        // Detects the message received by the self-built Http service, and the update time is the receiving time
                    t.getEcho(obj)                          // ...
                } catch (e) {
                    Log(e)
                }
            }
        }
        Log("Waiting is over...", "#FF0000")
                
        var tbls = t.deal()                                  // Pair according to the recorded messages and check if there is any unpaired message. If there is, it means the signal is lost.
        LogStatus(_D(), "\n`" + JSON.stringify(tbls) + "`")
        Sleep(20000)
    }
}

Kết quả thử nghiệm

img

img

Sau một thời gian thử nghiệm, có thể quan sát thấy rằng phương pháp HTTP mất ít thời gian hơn trung bình so với phương pháp API.

Chiến lược này có một dịch vụ HTTP tích hợp để nhận tín hiệu. Phương pháp thử nghiệm này không quá nghiêm ngặt, và yêu cầu nên đến từ bên ngoài. Vì sự hiểu biết đơn giản, yếu tố này có thể bị bỏ qua. Đối với hai phương pháp thu thập tín hiệu, chiến lượcs tích hợp dịch vụ HTTP giảm một liên kết sau tất cả, và nên phản hồi nhanh hơn. Đối với sự ổn định tín hiệu, điều quan trọng hơn là tín hiệu không thể bị mất hoặc bỏ lỡ. Kết quả thử nghiệm cho thấy API mở rộng của nền tảng FMZ cũng ổn định, và không có mất tín hiệu nào được quan sát trong thử nghiệm, nhưng không thể loại trừ rằng các yếu tố như mạng có thể gây ra các vấn đề tín hiệu. Sử dụng dịch vụ Http tích hợp để nhận tín hiệu bên ngoài trực tiếp cũng là một giải pháp tốt hơn.

Bài viết này chỉ là một điểm khởi đầu, chào đón để thảo luận, cảm ơn bạn đã đọc.


Thêm nữa