В процессе загрузки ресурсов... загрузка...

Дискуссия по внешнему приему сигнала платформы FMZ: расширенный API VS стратегия встроенного HTTP-сервиса

Автор:FMZ~Lydia, Создан: 2024-12-13 13:12:31, Обновлено: 2024-12-16 11:32:35

img

Преамбула

В платформе Digest есть несколько статей о подключении к веб-хукам Trading View, что позволяет стратегиям управлять торговлей с помощью сигналов из внешних систем.CommandRobot, проще говоря, http/https запрос внешнего сигнала отправляется на платформу FMZ, и платформа пересылает сигнал в качестве сообщения об взаимодействии стратегии в стратегическую программу.

По мере развития и итерации платформы многие новые функции были обновлены и модернизированы. Существуют также новые решения для приема внешних сигналов. Каждое решение имеет свои преимущества.

Использование платформы FMZ для расширения интерфейса API

Преимущества использования этого метода для подключения к внешним системам заключаются в том, что он относительно прост, очень безопасен и опирается на стабильность расширенного интерфейса API платформы.

Процесс приема внешних сигналов:

Внешняя система (Trading View webhook) > FMZ расширенный сервис API > Strategy live trading

  1. Внешняя система (Trading View webhook): Например, скрипт PINE, работающий на Trading View, может установить тревогу, и при запуске он отправит http-запрос в установленный URL-адрес webhook в качестве сигнала.
  2. FMZ расширенный сервис API: После успешного доступа к интерфейсу платформа передает информацию и отправляет ее в стратегию в виде интерактивного сообщения.
  3. Стратегия живая торговля: в стратегии живая торговля, вы можете разработать функцию GetCommand, чтобы слушать интерактивное сообщение, и выполнить указанную операцию после обнаружения сообщения.

По сравнению с использованием встроенного сервиса Http для создания сервиса для прямого приема сигналов, есть дополнительный шаг посередине (передача платформы).

Стратегия Встроенный HTTP-сервис

После того, как платформа поддерживает встроенную функцию сервиса HTTP языка JavaScript, вы можете создать параллельный сервис для непосредственного прослушивания внешних сигналов. Преимущества: созданный сервис HTTP является отдельной нитью и не влияет на логику основных функций. Он может слушать сообщения, такие как функция GetCommand и слушать внешние сигналы непосредственно. По сравнению с использованием расширенного решения API, это устраняет необходимость транзита.

Процесс приема внешних сигналов:

Внешняя система (Trading View webhook) > Стратегия торговли в реальном времени

  1. Внешняя система (Trading View webhook): Например, скрипт PINE, работающий на Trading View, может задать тревогу, которая отправит http-запрос на установленный URL-адрес webhook в качестве сигнала при запуске.
  2. Стратегия живой торговли: Стратегия запускает HTTP-сервис одновременно для получения внешних сигналов напрямую.

Это решение экономит шаг, но для улучшения безопасности лучше настроить https-сервис, который требует некоторых усилий.

Код испытания

Проверьте два решения. Следующая стратегия будет отправлять 10 запросов Http/Https параллельно в каждом цикле для имитации внешних сигналов. Затем стратегия отслеживает сообщения взаимодействия и сообщения, отправляемые потоками службы Http. Затем программа стратегии сопоставляет внешние сигналы и полученные сигналы один за другим, обнаруживает, есть ли потеря сигнала, и рассчитывает потребление времени.

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)
    }
}

При тестировании необходимо заполнить конкретный IP-адрес сервера и расширенный API-Ключ платформы FMZ.

var httpUrl = "http://123.123.123.123:8088/CommandRobot"
var accessKey = "xxx"
var secretKey = "xxx"
  1. Функция serverFunc создает одновременную службу HTTP для мониторинга внешних сигналов. Для внешних сообщений, полученных расширенным интерфейсом API, для мониторинга используется функция GetCommand.
  • Сообщения, отправленные через нить HTTP-сервиса: контролируетсяvar msg = threading.mainThread().peekMessage(-1).

  • Сообщения взаимодействия, пересылаемые расширенным интерфейсом API: контролируетсяvar cmd = GetCommand().

  1. Платформа оптимизирует основной многопоточный механизм восстановления ресурсов для одновременных функций, таких как:Threadилиexchange.Go, нет необходимости явно ждать завершения одновременных задач (таких как объединение функций, ожидание функций и т. Д.).
    // 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

Далее, давайте посмотрим на процесс тестирования, где информация аннотируется непосредственно в коде:

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)
    }
}

Результаты испытаний

img

img

После периода тестирования можно наблюдать, что метод HTTP занимает в среднем немного меньше времени, чем метод API.

Стратегия имеет встроенный Http-сервис для приема сигналов. Этот метод тестирования не очень строгий, и запрос должен исходить извне. Для простого понимания этот фактор можно игнорировать. Для двух методов получения сигнала встроенный Http-сервис сокращает одно соединение, и должен реагировать быстрее. Для стабильности сигнала более важно, чтобы сигнал не мог быть потерян или пропущен. Результаты тестирования показывают, что расширенный API платформы FMZ также стабилен, и в тесте не наблюдалось потери сигнала, но нельзя исключить, что такие факторы, как сеть, могут вызвать проблемы с сигналом. Использование встроенного Http-сервиса для прямого приема внешних сигналов также является лучшим решением.

Эта статья - только начало, добро пожаловать обсудить, спасибо за чтение.


Больше