Diskusi tentang penerimaan sinyal eksternal platform FMZ: API yang diperluas vs strategi layanan HTTP bawaan

Dibuat di: 2024-12-12 18:33:26, diperbarui pada: 2024-12-16 09:15:23
comments   0
hits   206

Diskusi tentang penerimaan sinyal eksternal platform FMZ: API yang diperluas vs strategi layanan HTTP bawaan

Kata pengantar

Ada beberapa artikel di pustaka platform tentang menghubungkan ke webhook Trading View, yang memungkinkan strategi untuk mendorong perdagangan berdasarkan sinyal dari sistem eksternal. Saat itu, platform tersebut tidak memiliki fungsi layanan http bawaan yang mendukung bahasa JavaScript. Antarmuka API yang diperluas dari platform digunakan:CommandRobotSederhananya, permintaan http/https dari sinyal eksternal dikirim ke platform FMZ, dan platform meneruskan sinyal sebagai pesan interaksi strategi ke program strategi.

Seiring dengan perkembangan dan pembaruan platform, banyak fitur baru yang ditingkatkan dan diperbarui. Ada juga solusi baru untuk menerima sinyal eksternal. Setiap solusi memiliki kelebihannya sendiri. Dalam artikel ini, kita akan membahas topik ini bersama-sama.

Gunakan platform FMZ untuk memperluas antarmuka API

Keuntungan menggunakan metode ini untuk terhubung ke sistem eksternal adalah relatif sederhana, sangat aman, dan bergantung pada stabilitas antarmuka API platform yang diperluas.

Proses penerimaan sinyal eksternal:

Sistem eksternal (webhook Trading View) –> Layanan API FMZ yang diperluas –> Strategi pasar riil

  1. Sistem eksternal (webhook Trading View): Misalnya, skrip PINE yang berjalan di Trading View dapat mengatur alarm, yang akan mengirimkan permintaan http ke alamat url webhook yang ditetapkan sebagai sinyal saat dipicu.
  2. Layanan API FMZ yang diperluas: Setelah berhasil mengakses antarmuka, platform meneruskan informasi dan mengirimkannya ke pasar nyata yang strategis sebagai pesan interaktif.
  3. Implementasi strategi: Dalam implementasi strategi, Anda dapat merancang fungsi GetCommand untuk mendengarkan pesan interaktif dan menjalankan operasi yang ditentukan setelah mendeteksi pesan.

Dibandingkan dengan menggunakan layanan Http bawaan untuk langsung membuat layanan guna menerima sinyal, ada langkah tambahan di tengah (transfer platform).

Strategi layanan HTTP bawaan

Setelah platform mendukung fungsi layanan Http bawaan bahasa JavaScript, Anda dapat langsung membuat layanan bersamaan untuk mendengarkan sinyal eksternal. Keunggulannya adalah: layanan Http yang dibuat merupakan utas terpisah dan tidak memengaruhi logika fungsi utama. Layanan ini dapat memantau pesan seperti fungsi GetCommand dan memantau sinyal eksternal secara langsung. Dibandingkan dengan menggunakan solusi API yang diperluas, layanan ini menghilangkan tautan transfer.

Proses penerimaan sinyal eksternal:

Sistem eksternal (webhook Trading View) –> Strategi perdagangan langsung

  1. Sistem eksternal (webhook Trading View): Misalnya, skrip PINE yang berjalan di Trading View dapat menyetel alarm. Saat dipicu, skrip tersebut akan mengirimkan permintaan http ke alamat url webhook yang ditetapkan sebagai sinyal.
  2. Implementasi strategi: Strategi secara bersamaan menjalankan layanan Http untuk menerima sinyal eksternal secara langsung.

Solusi ini menghemat satu langkah, tetapi untuk meningkatkan keamanan, sebaiknya mengonfigurasi layanan https, yang memerlukan beberapa upaya. Ini sedikit lebih merepotkan dibandingkan dengan menggunakan API yang diperluas.

Kode uji

Uji dua solusi. Strategi berikut akan mengirimkan 10 permintaan HTTP/HTTPS secara bersamaan di setiap siklus untuk mensimulasikan sinyal eksternal. Kemudian strategi tersebut memantau “pesan interaksi” dan “pesan yang dikirim oleh utas layanan Http”. Kemudian program strategi mencocokkan pesan sinyal eksternal dengan sinyal yang diterima satu per satu, mendeteksi apakah ada kehilangan sinyal, dan menghitung konsumsi waktu.

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("测试开始...", "#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("等待结束...", "#FF0000")
                
        var tbls = t.deal()
        LogStatus(_D(), "\n`" + JSON.stringify(tbls) + "`")
        Sleep(20000)
    }
}

Jika menguji, Anda perlu mengisi alamat IP server spesifik dan KUNCI API yang diperluas dari platform FMZ.

var httpUrl = "http://123.123.123.123:8088/CommandRobot"
var accessKey = "xxx"
var secretKey = "xxx"
  1. Fungsi serverFunc membuat layanan Http bersamaan untuk memantau sinyal eksternal. Untuk pesan eksternal yang diterima melalui antarmuka API yang diperluas, fungsi GetCommand digunakan untuk memantau.
  • Pesan yang dikirim oleh utas layanan Http: Bergantung padavar msg = threading.mainThread().peekMessage(-1)memantau.

  • Pesan interaksi yang diteruskan oleh antarmuka API yang diperluas: Bergantung padavar cmd = GetCommand()memantau.

  1. Baik proses pengiriman maupun penerimaan sinyal bersifat non-blocking. Platform mengoptimalkan mekanisme daur ulang sumber daya multi-threaded yang mendasarinya.Threadatauexchange.GoFungsi konkuren tidak perlu lagi secara eksplisit menunggu tugas konkuren selesai (seperti fungsi gabungan, fungsi tunggu, dsb.), dan sistem yang mendasarinya akan secara otomatis menangani daur ulang sumber daya (memerlukan versi host terbaru untuk mendukung hal ini).
    // 摘录代码片段,发送信号
    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)                       // 并发调用,非阻塞
        }
    }

    // 摘录代码片段,接收信号
    var cmd = GetCommand()                              // 监听来自扩展API的消息,非阻塞
    var msg = threading.mainThread().peekMessage(-1)    // 监听来自自建Http服务的消息,使用了参数-1,非阻塞

Selanjutnya, mari kita lihat proses pengujian ini, di mana informasinya diberi anotasi langsung dalam kode:

function main() {
    __Serve("http://0.0.0.0:8088", serverFunc)      // 在当前策略实例中,创建一个并发的http服务

    var t = createMsgTester(accessKey, secretKey, httpUrl)   // 创建一个用于测试管理的对象
    while (true) {                                           // 策略主循环开始
        Log("测试开始...", "#FF0000")
        t.run()                                              // 每次循环开始,调用测试管理对象的run函数,使用两种方式(1、通过扩展API发送信号,2、直接向当前策略创建的Http服务发送信号),每种方式并发发送10个请求

        var beginTS = new Date().getTime()
        while (new Date().getTime() - beginTS < 60 * 1000) {   // 循环检测来自扩展API的交互消息,循环检测来自自建Http服务的消息
            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()        // 检测到自建的Http服务收到的消息,更新时间为收到时间
                    t.getEcho(obj)                          // ...
                } catch (e) {
                    Log(e)
                }
            }
        }
        Log("等待结束...", "#FF0000")
                
        var tbls = t.deal()                                  // 根据记录的消息,配对,检查是否有未配对的消息,如果有说明有信号丢失
        LogStatus(_D(), "\n`" + JSON.stringify(tbls) + "`")
        Sleep(20000)
    }
}

Hasil Uji Coba

Diskusi tentang penerimaan sinyal eksternal platform FMZ: API yang diperluas vs strategi layanan HTTP bawaan

Diskusi tentang penerimaan sinyal eksternal platform FMZ: API yang diperluas vs strategi layanan HTTP bawaan

Setelah periode pengujian, dapat diamati bahwa metode Http membutuhkan waktu rata-rata sedikit lebih sedikit daripada metode API.

Strategi ini memiliki layanan Http bawaan untuk menerima sinyal. Metode pengujian ini tidak terlalu ketat dan permintaan harus datang dari luar. Demi kesederhanaan, faktor ini dapat diabaikan. Untuk kedua metode akuisisi sinyal, layanan Http bawaan strategi mengurangi satu tautan, dan seharusnya memiliki kecepatan respons yang lebih cepat. Untuk stabilitas sinyal, yang lebih penting adalah sinyal tidak hilang atau terlewat. Hasil pengujian menunjukkan bahwa API yang diperluas dari platform FMZ juga stabil. Tidak ada kehilangan sinyal yang diamati selama pengujian, tetapi tidak menutup kemungkinan bahwa berbagai faktor seperti jaringan dapat menyebabkan masalah sinyal. Menggunakan layanan Http bawaan untuk menerima sinyal eksternal secara langsung juga merupakan cara yang lebih baik. rencana.

Artikel ini hanyalah titik awal. Layanan Http bawaan dalam kode tersebut tidak diverifikasi dan hanya menerima data pesan. Dalam artikel berikutnya, kami akan sepenuhnya menerapkan templat layanan Http bawaan yang dapat digunakan untuk menerima sinyal Trading View eksternal. Selamat datang untuk berdiskusi. Terima kasih telah membaca.