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

FMZ 플랫폼 외부 신호 수신에 대한 탐구: 확장 API vs 전략 내장 HTTP 서비스

저자:발명가들의 수량화 - 작은 꿈, 창작: 2024-12-12 18:33:26, 업데이트: 2024-12-16 09:15:23

img

전문

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

플랫폼이 발전하고, 반복되고, 업그레이드되면서 많은 기능이 업데이트됩니다. 외부 신호를 수신하는 새로운 옵션도 있습니다. 다양한 옵션은 각자의 장점을 가지고 있습니다.

FMZ 플랫폼을 사용하여 API를 확장

이러한 방식으로 외부 시스템을 연결하는 것은 비교적 간단하고 보안이 강하며 플랫폼에 의존하는 확장 API 인터페이스의 안정성이 높다는 장점이 있습니다.

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

외부 시스템 (Trading View webhook) > FMZ 확장 API 서비스 > 전략 실제 디스크

1., 외부 시스템 (Trading View webhook): 예를 들어 Trading View에서 실행되는 PINE 스크립트처럼, 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 뷰에서 실행되는 트레이딩 워크에서 실행되는 트레이딩 워크에서 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("测试开始...", "#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 주소를 입력해야 합니다. FMZ 플랫폼의 확장 API KEY는

var httpUrl = "http://123.123.123.123:8088/CommandRobot"
var accessKey = "xxx"
var secretKey = "xxx"

1, serverFunc 함수는 외부 신호를 경청하기 위해 동행 HTTP 서비스를 만듭니다. 확장 API 인터페이스에서 수신되는 외부 메시지에 대해서는 GetCommand 함수를 사용하여 경청합니다.

  • https 서비스의 스레드에서 전송된 메시지: var msg = threading.mainThread().peekMessage(-1): :

  • 확장 API를 통해 전송되는 대화 메시지: var cmd = GetCommand(): :

2, 전송 신호와 수신 신호의 과정이 차단되지 않고, 플랫폼은 하위 다중 유선 자원 회수 메커니즘을 최적화하여,Thread또는exchange.Go병행 함수 (join 함수, wait 함수 등) 를 더 이상 뚜렷하게 기다리지 않고 시스템 하층에서 자원의 회수를 자동으로 처리합니다.

    // 摘录代码片段,发送信号
    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)
    }
}

테스트 결과

img

img

한동안의 테스트를 통해 HTTP 방식이 API 방식보다 평균적으로 조금 더 오래 걸리는 것을 관찰할 수 있습니다.

전략 내장 HTTP 서비스 수신 신호, 이러한 테스트 방법은 매우 엄격하지 않습니다, 요청은 외부에서 온해야합니다. 단순하게 이해하기 위해, 이 요소를 무시 할 수 있습니다. 두 가지 방식의 신호 획득에 대한 전략 내장 HTTP 서비스는 결국 한 루프를 줄이고 응답 속도가 조금 더 빠르어야합니다. 신호 안정성에 대해, 신호가 손실되지 않는 것이 중요합니다. 테스트 결과는 FMZ 플랫폼의 확장 API가 테스트에서 신호 손실을 볼 수 없지만 네트워크와 같은 모든 측면의 요소가 신호 문제를 일으키는 것을 배제하지 않고 신호를 직접 수신하는 내장 HTTP 서비스를 사용하는 것이 비교적 좋은 솔루션이라고 볼 수 있습니다.

이 문서는 텍스트 코드의 내장 HTTP 서비스가 검증을 하지 않고 단순한 메시지 데이터를 수신하는 것에 대해 언급하고 있습니다. 다음 문서는 외부 트레이딩 뷰 신호를 수신하는 데 사용할 수 있는 내장 HTTP 서비스 템플릿을 함께 구현했습니다.


더 많은