FMZ定量化に基づく注文同期管理システムの設計(1)

作成日:: 2022-02-14 19:46:30, 更新日:: 2023-09-15 20:44:11
comments   11
hits   1526

FMZ定量化に基づく注文同期管理システムの設計(1)

FMZ定量化に基づく注文同期管理システムの設計(1)

FMZ ライブラリの以前の記事では、いくつかの順序と位置の同期戦略を設計しました。

これらは、参照アカウントと同期アカウントを 1 つの戦略にまとめ、注文とポジションを管理および同期するためのものです。今日は、別の設計を試してみます。FMZ 定量取引プラットフォームの強力な拡張 API インターフェイスに基づいて、注文同期管理システムを設計します。

デザインのアイデア

まず第一に、良い提案とニーズが必要です。上記の 2 つの注文とポジションの同期戦略には、明らかな問題点がいくつかあります。それらについて一緒に説明しましょう。

  • 1. 同期戦略の実装者は、参照アカウントの交換 API キーと同期アカウントの交換 API キーを持っている必要があります。 この質問の使用シナリオは、他の取引所アカウントが自分のアカウントの 1 つをフォローしても問題がないということです。ただし、参照アカウントと同期アカウントの所有者が同じでない場合は面倒です。セキュリティ上の懸念から、同期されたアカウントの所有者が自分の取引所アカウントの API キーを提供したがらない場合があります。しかし、API KEY を提供せずに同期的に注文するにはどうすればよいでしょうか?

解決: FMZの拡張APIインターフェースを使用すると、同期アカウントの所有者(フォロワー)は、FMZ定量取引プラットフォームを登録し、戦略を実行するだけで済みます(この記事で設計されたシステムでは、订单同步管理系统(Synchronous Server)戦略(実際の市場)。次に、FMZ拡張API KEY(取引所アカウントのAPI KEYではないことに注意してください)と注文同期管理システム(同期サーバー)のリアルタイムIDを参照アカウントの所有者(注文を持ち込む人)に提供します。 。 アカウント所有者(注文者)の実際の注文を参照する場合(この記事で設計したシステムの場合)订单同步管理系统类库(Single Server))がシグナルを送信すると、同期されたアカウント所有者の実際のアカウントが取引シグナルを受信し、その後自動的に注文を出します。

    1. 多くの開発者はより優れた戦略を持っていますが、上記の 2 つの過去の注文とポジションの同期戦略を使用することはできません。なぜなら、そのためには、独自の戦略をこれらの同期戦略と統合する必要があり、戦略を全面的に見直す必要があるかもしれないため、時間と労力がかかるからです。成熟した戦略の一部を注文同期機能に直接アップグレードする良い方法はありますか? 解決: 注文同期テンプレートライブラリを設計することができます(この記事で設計したシステム订单同步管理系统类库(Single Server)ストラテジー)により、参照アカウントの所有者(注文を受ける人)は、このテンプレートライブラリを自分のストラテジーに直接埋め込むことで、注文とポジションの同期機能を実現できます。
    1. 余分な実数順序を 1 つ減らします。 最後の問題点は、上で説明した 2 つの過去の注文とポジションの同期戦略を使用する場合です。参照口座(単一口座)のポジションを監視するには、追加のリアル口座を開設する必要があります。 解決: テンプレート ライブラリを使用して、参照アカウント戦略に機能を埋め込みます。

したがって、このシステムは 2 つの部分で構成されます。 1. 注文同期管理システムクラスライブラリ(シングルサーバー) 2. 注文同期管理システム(同期サーバー)

要件が明確になったので、設計を始めましょう。

設計 1: 注文同期管理システム クラス ライブラリ (単一サーバー)

これは戦略ではないことに注意してください。これは FMZ のテンプレート クラス ライブラリです。テンプレート クラス ライブラリの概念は FMZ API ドキュメントで検索できるので、ここでは詳細には触れません。

テンプレートライブラリコード:

// 全局变量
var keyName_label = "label"
var keyName_robotId = "robotId"
var keyName_extendAccessKey = "extendAccessKey"
var keyName_extendSecretKey = "extendSecretKey"
var fmzExtendApis = parseConfigs([config1, config2, config3, config4, config5])
var mapInitRefPosAmount = {}

function parseConfigs(configs) {
    var arr = []
    _.each(configs, function(config) {
        if (config == "") {
            return 
        }
        var strArr = config.split(",")
        if (strArr.length != 4) {
            throw "configs error!"
        }
        var obj = {}
        obj[keyName_label] = strArr[0]
        obj[keyName_robotId] = strArr[1]
        obj[keyName_extendAccessKey] = strArr[2]
        obj[keyName_extendSecretKey] = strArr[3]
        arr.push(obj)
    })
    return arr 
}

function getPosAmount(pos, ct) {
    var longPosAmount = 0
    var shortPosAmount = 0
    _.each(pos, function(ele) {
        if (ele.ContractType == ct && ele.Type == PD_LONG) {
            longPosAmount = ele.Amount
        } else if (ele.ContractType == ct && ele.Type == PD_SHORT) {
            shortPosAmount = ele.Amount
        }
    })
    var timestamp = new Date().getTime()
    return {ts: timestamp, long: longPosAmount, short: shortPosAmount}
}

function sendCommandRobotMsg (robotId, accessKey, secretKey, msg) {
    // https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyyy&method=CommandRobot&args=[186515,"ok12345"]
    var url = "https://www.fmz.com/api/v1?access_key=" + accessKey + "&secret_key=" + secretKey + "&method=CommandRobot&args=[" + robotId + ',"' + msg + '"]'
    Log(url)
    var ret = HttpQuery(url)
    return ret 
}

function follow(nowPosAmount, symbol, ct, type, delta) {
    var msg = ""
    var nowAmount = type == PD_LONG ? nowPosAmount.long : nowPosAmount.short
    if (delta > 0) {
        // 开仓
        var tradeDirection = type == PD_LONG ? "buy" : "sell"
        // 发送信号
        msg = symbol + "," + ct + "," + tradeDirection + "," + Math.abs(delta)        
    } else if (delta < 0) {
        // 平仓
        var tradeDirection = type == PD_LONG ? "closebuy" : "closesell"
        if (nowAmount <= 0) {
            Log("未检测到持仓")
            return 
        }
        // 发送信号
        msg = symbol + "," + ct + "," + tradeDirection + "," + Math.abs(delta)
    } else {
        throw "错误"
    }
    if (msg) {
        _.each(fmzExtendApis, function(extendApiConfig) {
            var ret = sendCommandRobotMsg(extendApiConfig[keyName_robotId], extendApiConfig[keyName_extendAccessKey], extendApiConfig[keyName_extendSecretKey], msg)
            Log("调用CommandRobot接口,", "label:", extendApiConfig[keyName_label], ", msg:", msg, ", ret:", ret)
            Sleep(1000)
        })
    }
}

$.PosMonitor = function(exIndex, symbol, ct) {    
    var ts = new Date().getTime()
    var ex = exchanges[exIndex]
    // 判断ex类型
    var exName = ex.GetName()
    var isFutures = exName.includes("Futures_")
    var exType = isFutures ? "futures" : "spot"
    if (!isFutures) {
        throw "仅支持期货跟单"
    }

    if (exType == "futures") {
        // 缓存 symbol ct
        var buffSymbol = ex.GetCurrency()
        var buffCt = ex.GetContractType()

        // 切换到对应的交易对、合约代码
        ex.SetCurrency(symbol)
        if (!ex.SetContractType(ct)) {
            throw "SetContractType failed"
        }

        // 监控持仓
        var keyInitRefPosAmount = "refPos-" + exIndex + "-" + symbol + "-" + ct    // refPos-exIndex-symbol-contractType
        var initRefPosAmount = mapInitRefPosAmount[keyInitRefPosAmount]
        if (!initRefPosAmount) {
            // 没有初始化数据,初始化          
            mapInitRefPosAmount[keyInitRefPosAmount] = getPosAmount(_C(ex.GetPosition), ct)
            initRefPosAmount = mapInitRefPosAmount[keyInitRefPosAmount]
        }

        // 监控
        var nowRefPosAmount = getPosAmount(_C(ex.GetPosition), ct)
        // 计算仓位变动
        var longPosDelta = nowRefPosAmount.long - initRefPosAmount.long
        var shortPosDelta = nowRefPosAmount.short - initRefPosAmount.short

        // 检测变动
        if (!(longPosDelta == 0 && shortPosDelta == 0)) {
            // 执行多头动作
            if (longPosDelta != 0) {
                Log(ex.GetName(), ex.GetLabel(), symbol, ct, "执行多头跟单,变动量:", longPosDelta)
                follow(nowRefPosAmount, symbol, ct, PD_LONG, longPosDelta)
            }
            // 执行空头动作
            if (shortPosDelta != 0) {
                Log(ex.GetName(), ex.GetLabel(), symbol, ct, "执行空头跟单,变动量:", shortPosDelta)
                follow(nowRefPosAmount, symbol, ct, PD_SHORT, shortPosDelta)
            }

            // 执行跟单操作后,更新
            mapInitRefPosAmount[keyInitRefPosAmount] = nowRefPosAmount
        }

        // 恢复 symbol ct
        ex.SetCurrency(buffSymbol)
        ex.SetContractType(buffCt)
    } else if (exType == "spot") {
        // 现货
    }
}

$.getTbl = function() {
    var tbl = {
        "type" : "table", 
        "title" : "同步数据", 
        "cols" : [], 
        "rows" : []
    }
    // 构造表头
    tbl.cols.push("监控账户:refPos-exIndex-symbol-contractType")
    tbl.cols.push(`监控持仓:{"时间戳":xxx,"多头持仓量":xxx,"空头持仓量":xxx}`)
    _.each(fmzExtendApis, function(extendApiData, index) {
        tbl.cols.push(keyName_robotId + "-" + index)
    })
    
    // 写入数据
    _.each(mapInitRefPosAmount, function(initRefPosAmount, key) {
        var arr = [key, JSON.stringify(initRefPosAmount)]
        _.each(fmzExtendApis, function(extendApiData) {
            arr.push(extendApiData[keyName_robotId])
        })
        tbl.rows.push(arr)
    })

    return tbl
}

// 引用该模板类库的策略调用范例
function main() {
    // 清除所有日志
    LogReset(1)

    // 切换到OKEX 模拟盘测试
    exchanges[0].IO("simulate", true)

    // 设置合约
    exchanges[0].SetCurrency("ETH_USDT")
    exchanges[0].SetContractType("swap")

    // 定时交易时间间隔
    var tradeInterval = 1000 * 60 * 3        // 三分钟交易一次,用于观察跟单信号
    var lastTradeTS = new Date().getTime()
    
    while (true) {
        // 策略其它逻辑...

        // 用于测试的模拟交易触发
        var ts = new Date().getTime()
        if (ts - lastTradeTS > tradeInterval) {
            Log("模拟带单策略发生交易,持仓变化", "#FF0000")
            exchanges[0].SetDirection("buy")
            exchanges[0].Buy(-1, 1)
            lastTradeTS = ts
        }

        // 使用模板的接口函数
        $.PosMonitor(0, "ETH_USDT", "swap")    // 可以设置多个监控,监控带单策略上的不同的exchange对象  
        var tbl = $.getTbl()
        
        // 显示状态栏
        LogStatus(_D(), "\n" + "`" + JSON.stringify(tbl) + "`")
        Sleep(1000)
    }
}

設計は非常にシンプルで、このクラス ライブラリには 2 つの機能があります。 FMZプラットフォーム上のプログラマティック取引戦略が参照する場合订单同步管理系统类库(Single Server)テンプレートライブラリの後。この戦略では次の機能を使用できます。

  • $.PosMonitor この機能は、戦略内の取引オブジェクトのポジション変更を監視し、テンプレートのパラメータで設定された実際の市場に取引シグナルを送信します: 注文同期管理システム クラス ライブラリ (シングル サーバー)。

  • $.getTbl 監視された同期データを返します。

使用例は次の場所にあります: 注文同期管理システムクラスライブラリ (シングルサーバー) テンプレートmain関数内:

// 引用该模板类库的策略调用范例
function main() {
    // 清除所有日志
    LogReset(1)

    // 切换到OKEX 模拟盘测试
    exchanges[0].IO("simulate", true)

    // 设置合约
    exchanges[0].SetCurrency("ETH_USDT")
    exchanges[0].SetContractType("swap")

    // 定时交易时间间隔
    var tradeInterval = 1000 * 60 * 3        // 三分钟交易一次,用于观察跟单信号
    var lastTradeTS = new Date().getTime()
    
    while (true) {
        // 策略其它逻辑...

        // 用于测试的模拟交易触发
        var ts = new Date().getTime()
        if (ts - lastTradeTS > tradeInterval) {
            Log("模拟带单策略发生交易,持仓变化", "#FF0000")
            exchanges[0].SetDirection("buy")
            exchanges[0].Buy(-1, 1)
            lastTradeTS = ts
        }

        // 使用模板的接口函数
        $.PosMonitor(0, "ETH_USDT", "swap")    // 可以设置多个监控,监控带单策略上的不同的exchange对象  
        var tbl = $.getTbl()
        
        // 显示状态栏
        LogStatus(_D(), "\n" + "`" + JSON.stringify(tbl) + "`")
        Sleep(1000)
    }
}

テンプレート ライブラリ自体も実際の戦略を作成することができ、通常はテンプレート ライブラリをテストするために使用されます。たとえば、このテンプレートのテスト。テンプレートを理解できるmain関数はあなた自身の戦略です。main関数。

OKEXシミュレーションディスクテストを使用するためのテストコードが書かれています。FMZでOKEXシミュレーションディスクAPI KEYを参照アカウント(注文あり)として設定し、メイン関数でシミュレーションディスクへの切り替えを開始する必要があります。次に、取引ペアを ETH_USDT に設定し、契約を永久(スワップ)に設定します。次に、while ループを入力します。戦略取引のトリガーをシミュレートするために、サイクル内で 3 分ごとに注文が配置されます。 whileループは$.PosMonitor(0, "ETH_USDT", "swap")この関数の最初のパラメータは0として渡され、監視交換を示します。[[0] この交換オブジェクトは、ETH_USDT 取引ペアとスワップ契約を監視します。それから電話する$.getTbl()チャート情報を取得するには、LogStatus(_D(), "\n" + "” + JSON.stringify(tbl) + “")ステータス バーにチャート データを表示できるようにします。

つまり、このテンプレートを参照するポリシーで使用する限り、$.PosMonitor(0, "ETH_USDT", "swap")、この戦略には、特定の商品の位置を監視し、位置の変化に基づいてメッセージを送信する機能を備えることができます。

テストの前に説明してください订单同步管理系统类库(Single Server)戦略パラメータ設計: テンプレート インターフェース機能を使用して、戦略アップグレードに単一の機能を持たせる方法について説明しました。では、位置が変わったときに信号は誰に送信されるのでしょうか? この質問が誰に送られるかは、订单同步管理系统类库(Single Server)設定するパラメータ。

FMZ定量化に基づく注文同期管理システムの設計(1)

5 つのパラメーターがあり、最大 5 回のプッシュをサポートしていることがわかります (増やす必要がある場合は拡張できます)。パラメーターのデフォルトは空の文字列であり、処理されないことを意味します。設定文字列の形式: label、robotId、accessKey、secretKey

  • label 同期されたアカウントのラベルは、特定のアカウントをマークするために使用され、名前は任意に設定できます。

  • robotId 同期アカウントの所有者によって作成された実ID订单同步管理系统(Synchronous Server)実際のトランザクションの ID。

  • accessKey FMZの拡張APIアクセスキー

  • secretKey FMZの拡張API secretKey

次に簡単なテストを行います。

注文同期管理システムクラスライブラリ(シングルサーバー)実ディスク操作:

FMZ定量化に基づく注文同期管理システムの設計(1)

注文同期管理システム (同期サーバー) が信号を受信しました: 注文同期管理システム (同期サーバー) の設計はまだ完了していません。まずは、トランザクションを実行せず、シグナルのみを出力する簡単なコードで実装してみましょう。

注文同期管理システム(同期サーバー)の一時コード:

function main() {
    LogReset(1)
    while (true) {
        var cmd = GetCommand()
        if (cmd) {
            // cmd: ETH_USDT,swap,buy,1
            Log("cmd: ", cmd)
        }
        Sleep(1000)
    }
}

FMZ定量化に基づく注文同期管理システムの設計(1)

同期アカウント所有者の実際のアカウントが情報を受信したことがわかります。ETH_USDT,swap,buy,1。 このように、次のステップは、情報内の取引ペア、契約コード、取引方向、数量に応じて取引を自動的に追跡することです。

現在のところ订单同步管理系统(Synchronous Server)これは単なる一時的なコードであり、次号ではその設計について引き続き検討します。