
В библиотеке платформы было несколько статей о подключении к веб-хукам Trading View, которые позволяли стратегиям управлять сделками на основе сигналов от внешних систем. В то время платформа не имела встроенной функции http-сервиса, которая поддерживала бы язык JavaScript. Используется расширенный API-интерфейс платформы:CommandRobotПроще говоря, http/https-запрос внешнего сигнала отправляется на платформу FMZ, а платформа пересылает сигнал как сообщение о стратегическом взаимодействии в стратегическую программу.
По мере развития и совершенствования платформы многие новые функции были обновлены и модернизированы. Появились также новые решения для приема внешних сигналов. Каждое решение имеет свои преимущества. В этой статье мы вместе обсудим эту тему.
Преимущества использования этого метода для подключения к внешним системам заключаются в том, что он относительно прост, высокобезопасен и опирается на стабильность расширенного API-интерфейса платформы.
Процесс приема внешних сигналов:
Внешняя система (Trading View webhook) -> Расширенный API-сервис FMZ -> Стратегия реального рынка
По сравнению с использованием встроенной службы HTTP для непосредственного создания службы для приема сигналов, есть дополнительный шаг посередине (передача платформы).
После того, как платформа поддержит встроенную функцию HTTP-сервиса языка JavaScript, вы сможете напрямую создать параллельный сервис для прослушивания внешних сигналов. Преимущества: созданная служба Http является отдельным потоком и не влияет на логику основной функции. Она может отслеживать сообщения, такие как функция GetCommand, и напрямую отслеживать внешние сигналы. По сравнению с использованием расширенного решения API, она исключает ссылку на передачу .
Процесс приема внешних сигналов:
Внешняя система (Trading View webhook) -> Стратегия торговли в реальном времени
Это решение экономит один шаг, но для повышения безопасности лучше всего настроить службу https, что требует некоторых усилий. Это немного сложнее, чем использование расширенного API.
Протестируйте два решения. Следующая стратегия будет отправлять 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("测试开始...", "#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)
}
}
При тестировании вам необходимо указать конкретный IP-адрес сервера и расширенный API-КЛЮЧ платформы FMZ.
var httpUrl = "http://123.123.123.123:8088/CommandRobot"
var accessKey = "xxx"
var secretKey = "xxx"
Сообщения, отправляемые потоком службы Http:
Зависит отvar msg = threading.mainThread().peekMessage(-1)монитор.
Сообщения взаимодействия, пересылаемые расширенным интерфейсом API:
Зависит отvar cmd = GetCommand()монитор.
Threadилиexchange.GoПараллельным функциям больше не нужно явно ожидать завершения параллельных задач (например, функции соединения, функции ожидания и т. д.), а базовая система будет автоматически обрабатывать повторное использование ресурсов (для поддержки этой функции требуется последняя версия хоста). // 摘录代码片段,发送信号
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,非阻塞
Далее давайте рассмотрим этот процесс тестирования, где информация аннотируется непосредственно в коде:
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)
}
}


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