리소스 로딩... 로딩...

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

저자:FMZ~리디아, 창작: 2024-12-13 13:12:31, 업데이트: 2024-12-16 11:32:35

img

전문

플랫폼 다이게스트 (Digest) 에는 트레이딩 뷰 웹후크 (webhooks) 에 연결하는 것에 대한 여러 기사가 있습니다. 이는 전략이 외부 시스템에서 오는 신호로 거래를 수행 할 수있게합니다. 그 당시 플랫폼은 자바스크립트 언어의 내장 http 서비스 기능을 지원하지 않았습니다. 플랫폼의 확장 API 인터페이스는 사용되었습니다:CommandRobot간단히 말해서, 외부 신호의 http/https 요청은 FMZ 플랫폼에 전송되고, 플랫폼은 전략 상호 작용 메시지로서 신호를 전략 프로그램에 전달합니다.

플랫폼이 개발되고 반복됨에 따라 많은 새로운 기능이 업데이트되고 업그레이드되었습니다. 또한 외부 신호를 수신하기위한 새로운 솔루션이 있습니다. 각 솔루션에는 자신의 장점이 있습니다. 이 기사에서는이 주제에 대해 함께 논의 할 것입니다.

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

이러한 방법을 사용하여 외부 시스템과 연결하는 장점은 상대적으로 간단하고 매우 안전하며 플랫폼의 확장 된 API 인터페이스의 안정성에 의존한다는 것입니다.

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

외부 시스템 (Trading View webhook) > FMZ 확장 API 서비스 > 전략 라이브 거래

  1. 외부 시스템 (Trading View webhook): 예를 들어, Trading View에서 실행되는 PINE 스크립트는 경보를 설정할 수 있으며, 트리거되면 신호로 설정된 webhook url 주소로 http 요청을 보낼 것입니다.
  2. FMZ 확장 API 서비스: 인터페이스에 성공적으로 액세스 한 후 플랫폼은 정보를 전달하고 대화형 메시지로 전략 라이브 거래에 전송합니다.
  3. 전략 라이브 트레이딩: 전략 라이브 트레이딩에서는 GetCommand 함수를 디자인하여 대화형 메시지를 듣고 메시지를 감지한 후 지정된 작업을 실행할 수 있습니다.

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

전략 내장된 Http 서비스

플랫폼이 자바스크립트 언어의 내장된 Http 서비스 기능을 지원 한 후, 외부 신호를 직접 듣기 위해 동시 서비스를 만들 수 있습니다. 이점은: 생성 된 Http 서비스는 별도의 스레드이며 주요 기능 논리에 영향을 미치지 않습니다. GetCommand 기능과 같은 메시지를 듣고 외부 신호를 직접 들을 수 있습니다. 확장 된 API 솔루션 사용에 비해 트랜시트의 필요성을 제거합니다.

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

외부 시스템 (Trading View webhook) > 전략 실시간 거래

  1. 외부 시스템 (Trading View webhook): 예를 들어, Trading View에서 실행되는 PINE 스크립트는 알람을 설정할 수 있으며, 트리거될 때 신호로 설정된 webhook 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("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 주소를 입력하고 FMZ 플랫폼의 확장 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, 동시 작업이 완료되는 것을 명시적으로 기다릴 필요가 없습니다. 기본 시스템은 자원의 복구 작업을 자동으로 처리합니다. (이 작업을 지원하기 위해 최신 버전의 도커가 필요합니다.)
    // 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 서비스는 결국 하나의 링크를 줄이고, 더 빨리 응답해야 한다. 신호 안정성을 위해, 신호가 손실되거나 놓칠 수 없도록 하는 것이 더 중요하다. 테스트 결과는 FMZ 플랫폼의 확장된 API도 안정적이며, 테스트에서 신호 손실이 관찰되지 않았음을 보여 주지만, 네트워크와 같은 요인이 신호 문제를 일으킬 수 있다는 것을 배제할 수 없다. 내장된 Http 서비스를 사용하여 외부 신호를 직접 수신하는 것도 더 좋은 해결책이다.

이 기사 는 시작점 일 뿐 입니다. 토론 할 수 있도록 환영 합니다. 읽어주셔서 감사합니다.


더 많은