avatar of 发明者量化-小小梦 发明者量化-小小梦
집중하다 사신
4
집중하다
1271
수행원

FMZ 플랫폼의 외부 신호 수신에 대한 논의: 확장 API 대 전략적 내장 HTTP 서비스

만든 날짜: 2024-12-12 18:33:26, 업데이트 날짜: 2025-05-16 15:53:52
comments   0
hits   583

FMZ 플랫폼의 외부 신호 수신에 대한 논의: 확장 API 대 전략적 내장 HTTP 서비스

머리말

플랫폼 라이브러리에는 Trading View 웹훅에 연결하는 것에 대한 여러 문서가 있었는데, 이를 통해 외부 시스템의 신호에 따라 거래를 추진하는 전략을 사용할 수 있었습니다. 당시 플랫폼에는 JavaScript 언어를 지원하는 내장 http 서비스 기능이 없었습니다. 플랫폼의 확장된 API 인터페이스가 사용됩니다.CommandRobot간단히 말해서, 외부 신호의 http/https 요청은 FMZ 플랫폼으로 전송되고, 플랫폼은 해당 신호를 전략적 상호작용 메시지로 전략 프로그램에 전달합니다.

플랫폼이 개발되고 개선되면서 많은 새로운 기능이 업그레이드되고 업데이트되었습니다. 외부 신호를 수신하기 위한 새로운 솔루션도 있습니다. 각 솔루션에는 고유한 장점이 있습니다. 이 글에서는 이 주제를 함께 논의하겠습니다.

FMZ 플랫폼을 사용하여 API 인터페이스 확장

이 방법을 사용해 외부 시스템에 연결하는 장점은 비교적 간단하고, 보안성이 높으며, 플랫폼의 확장된 API 인터페이스의 안정성을 활용한다는 점입니다.

외부 신호를 수신하는 과정:

외부 시스템(Trading View 웹훅) –> FMZ 확장 API 서비스 –> 전략 실제 시장

  1. 외부 시스템(Trading View 웹훅): 예를 들어, Trading View에서 실행되는 PINE 스크립트는 알람을 설정할 수 있으며, 이 알람은 트리거되면 신호로 설정된 웹훅 URL 주소로 http 요청을 보냅니다.
  2. FMZ 확장 API 서비스: 인터페이스에 성공적으로 접근한 후, 플랫폼은 정보를 전달하고 이를 대화형 메시지로 전략적 실제 시장으로 전송합니다.
  3. 전략 구현: 전략 구현에서는 GetCommand 함수를 설계하여 대화형 메시지를 수신하고 메시지를 감지한 후 지정된 작업을 실행할 수 있습니다.

내장된 Http 서비스를 사용하여 신호를 수신하는 서비스를 직접 만드는 것과 비교하면 중간에 추가 단계(플랫폼 전송)가 있습니다.

전략 내장 Http 서비스

플랫폼이 JavaScript 언어의 내장된 Http 서비스 기능을 지원하면 외부 신호를 수신하기 위한 동시 서비스를 직접 만들 수 있습니다. 장점은 다음과 같습니다. 생성된 Http 서비스는 별도의 스레드이며 주요 기능 로직에 영향을 미치지 않습니다. GetCommand 함수와 같은 메시지를 모니터링하고 외부 신호를 직접 모니터링할 수 있습니다. 확장된 API 솔루션을 사용하는 것과 비교하면 전송 링크가 제거됩니다.

외부 신호를 수신하는 과정:

외부 시스템(Trading View 웹훅) –> 전략 라이브 트레이딩

  1. 외부 시스템(Trading View 웹훅): 예를 들어, Trading View에서 실행되는 PINE 스크립트는 알람을 설정할 수 있습니다. 트리거되면 설정된 웹훅 URL 주소로 신호로 http 요청을 보냅니다.
  2. 전략 구현: 전략은 외부 신호를 직접 수신하기 위해 Http 서비스를 동시에 실행합니다.

이 솔루션은 한 단계를 절약하지만 보안을 강화하려면 약간의 노력이 필요한 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)
    }
}

테스트하는 경우, FMZ 플랫폼의 특정 서버 IP 주소와 확장된 API 키를 입력해야 합니다.

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동시 함수는 더 이상 동시 작업이 완료될 때까지 명시적으로 기다릴 필요가 없습니다(예: 조인 함수, 대기 함수 등). 그리고 기본 시스템이 자동으로 리소스 재활용을 처리합니다(이를 지원하려면 최신 버전의 호스트가 필요함).
    // 摘录代码片段,发送信号
    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)
    }
}

테스트 결과

FMZ 플랫폼의 외부 신호 수신에 대한 논의: 확장 API 대 전략적 내장 HTTP 서비스

FMZ 플랫폼의 외부 신호 수신에 대한 논의: 확장 API 대 전략적 내장 HTTP 서비스

일정 기간 테스트해 본 결과, Http 메서드는 API 메서드보다 평균적으로 시간이 약간 덜 걸리는 것을 알 수 있습니다.

이 전략에는 신호를 수신하기 위한 내장된 Http 서비스가 있습니다. 이 테스트 방법은 그다지 엄격하지 않으며 요청은 외부에서 와야 합니다. 단순화를 위해 이 요소는 무시할 수 있습니다. 두 가지 신호 수집 방법 모두에서 전략에 내장된 Http 서비스는 결국 하나의 링크를 줄이고 더 빠른 응답 속도를 제공해야 합니다. 신호 안정성을 위해서는 신호가 손실되거나 놓치는 일이 없어야 합니다. 테스트 결과에 따르면 FMZ 플랫폼의 확장 API도 안정적입니다. 테스트 중에 신호 손실은 관찰되지 않았지만 네트워크 등 다양한 요인으로 인해 신호 문제가 발생할 가능성도 배제할 수 없습니다. 내장된 Http 서비스 사용 외부 신호를 직접 수신하는 것도 더 나은 방법입니다.

이 글은 단지 시작점일 뿐입니다. 코드의 내장 Http 서비스는 검증되지 않고 단순히 메시지 데이터를 수신합니다. 다음 글에서는 외부 Trading View 신호를 수신하기 위한 사용 가능한 내장 Http 서비스 템플릿을 완전히 구현합니다. 토론에 오신 것을 환영합니다. 읽어주셔서 감사합니다.