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

作成日:: 2024-12-12 18:33:26, 更新日:: 2024-12-16 09:15:23
comments   0
hits   212

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

序文

プラットフォーム ライブラリには、Trading View Webhook への接続に関する記事がいくつかあり、これにより、外部システムからのシグナルに基づいて戦略で取引を駆動できるようになりました。当時、プラットフォームには 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 Webhook) –> 戦略ライブ取引

  1. 外部システム (Trading View ウェブフック): たとえば、Trading View で実行されている PINE スクリプトはアラームを設定できます。トリガーされると、設定されたウェブフック 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("测试开始...", "#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 キーを入力する必要があります。

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 サービスにより、リンクが 1 つ削減され、応答速度が速くなるはずです。信号の安定性のためには、信号が失われたり、見逃されたりしないことがより重要です。テスト結果によると、FMZプラットフォームの拡張APIも安定しています。テスト中に信号損失は見られませんでしたが、ネットワークなどのさまざまな要因が信号の問題を引き起こす可能性は排除できません。組み込みのHttpサービスを使用する外部信号を直接受信することも、より良い方法です。計画。

この記事は単なる出発点です。コードに組み込まれている HTTP サービスは検証されておらず、単にメッセージ データを受信するだけです。次の記事では、外部の Trading View シグナルを受信するために使用できる組み込み HTTP サービス テンプレートを完全に実装します。議論へようこそ。読んでいただきありがとうございます。