資源の読み込みに... 荷物...

FMZプラットフォームの外部信号受信に関する議論:拡張API VS戦略内蔵HTTPサービス

作者: リン・ハーンFMZ~リディア作成日:2024年12月13日 13:12:31 更新日:2024年12月16日 11:32:35

img

前言

プラットフォームDigestには,外部システムからの信号で取引を行う戦略を可能にする,トレードビュー webhookに接続するいくつかの記事があります. 当時はプラットフォームはJavaScript言語の内蔵 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 サービス

プラットフォームがJavaScript言語の内蔵Httpサービス機能をサポートした後,外部信号を直接聞くために並行サービスを作成できます.利点としては:作成されたHttpサービスは別々のスレッドであり,メイン機能論理に影響しません.GetCommand機能のようなメッセージを聴き,外部信号を直接聞くことができます.拡張APIソリューションの使用と比較して,トランジットを必要としません.

外部信号を受信するプロセス:

外部システム (Trading View webhook) > ストラテジーライブ取引

  1. 外部システム (Trading View webhook):例えば,Trading View で実行される PINE スクリプトはアラームを設定することができ,トリガーされたとき,設定された webhook url アドレスに http リクエストを信号として送信します.
  2. ストラテジー ライブ・トレーディング: 外部の信号を直接受信するために,Httpサービスを同時に実行する戦略.

このソリューションはステップを節約しますが,セキュリティを向上させるために,いくつかの努力を要する https サービスを設定するのが良いです.拡張 API ソリューションを使用するよりも少し面倒です.

テストコード

2つのソリューションをテストする.次の戦略は,外部信号をシミュレートするために,各サイクルで並列に10つのHttp/Httpsリクエストを送信する.その後,戦略は,Httpサービススレッドによって推された"インタラクションメッセージ"と"メッセージ"を監視する.その後,戦略プログラムは,外部信号メッセージと受信された信号を1つずつマッチし,信号損失があるかどうかを検出し,時間の消費を計算する.

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サービスがあります.このテスト方法は非常に厳格ではなく,要求は外部から来るべきです.簡単な理解のために,この要因を無視することができます. 信号取得の2つの方法では,戦略の内蔵Httpサービスはいずれも1つのリンクを削減し,より速く応答する必要があります. 信号安定のために,信号が失われたり見逃したりできないことがより重要です. テスト結果は,FMZプラットフォームの拡張APIも安定しており,テストでは信号損失は観察されていませんが,ネットワークなどの要因が信号問題を引き起こす可能性があることを排除することはできません. 外部信号を直接受信するために内蔵Httpサービスを使用することはよりよい解決策です.

この記事 は 始まり の 点 だけ で あり,議論 に 歓迎 さ れ ます.読め た こと に ありがとう.


もっと