avatar of 发明者量化-小小梦 发明者量化-小小梦
집중하다 사신
4
집중하다
1271
수행원

FMZ 정량화 기반 주문 동기화 관리 시스템 설계(1)

만든 날짜: 2022-02-14 19:46:30, 업데이트 날짜: 2025-05-16 16:36:53
comments   11
hits   1939

FMZ 정량화 기반 주문 동기화 관리 시스템 설계(1)

FMZ 정량화 기반 주문 동기화 관리 시스템 설계(1)

FMZ 라이브러리의 이전 기사에서는 여러 가지 순서 및 위치 동기화 전략을 설계했습니다.

이는 참조 계정과 동기화 계정을 하나의 전략으로 통합하여 주문 및 포지션을 관리하고 동기화하기 위한 것입니다. 오늘은 다른 디자인을 시도해 보겠습니다. FMZ 양적 거래 플랫폼의 강력한 확장 API 인터페이스를 기반으로 주문 동기화 관리 시스템을 설계해 보겠습니다.

디자인 아이디어

우선 우리에게는 몇 가지 좋은 제안과 필요 사항이 필요합니다. 위의 두 가지 이전 순서 및 위치 동기화 전략에는 몇 가지 분명한 문제점이 있습니다. 함께 논의해 보겠습니다.

  • 1. 동기화 전략을 구현하는 사람은 참조 계정의 교환 API 키와 동기화 계정의 교환 API 키를 가지고 있어야 합니다. 이 질문의 사용 시나리오는 다음과 같습니다. 다른 거래소 계좌가 자신의 계정 중 하나를 팔로우하는 것은 문제가 없습니다. 하지만 참조 계정과 동기화 계정의 소유자가 동일하지 않으면 문제가 발생합니다. 때로는 보안 문제로 인해 동기화된 계정 소유자가 자신의 거래소 계정의 API 키를 제공하기를 원하지 않는 경우가 있습니다. 하지만 API 키를 제공하지 않고도 동기식으로 주문을 하려면 어떻게 해야 할까요?

해결책: FMZ의 확장된 API 인터페이스를 사용하면 동기화 계정 소유자(팔로워)는 FMZ 양적 거래 플랫폼을 등록한 다음 전략을 실행하기만 하면 됩니다(이 문서에서 설계한 시스템:订单同步管理系统(Synchronous Server)실제 시장의 전략). 그런 다음 FMZ 확장 API 키(거래소 계정의 API 키가 아님)와 주문 동기화 관리 시스템(동기 서버)의 실시간 ID를 참조 계정 소유자(주문을 가져오는 사람)에게 제공합니다. . 계정 소유자(주문을 한 사람)의 실제 주문을 참조할 때(본 문서에서 설계한 시스템에서)订单同步管理系统类库(Single Server))가 신호를 보내면 동기화된 계정 소유자의 실제 계정은 거래 신호를 받고 그 후 자동으로 주문을 내립니다.

    1. 많은 개발자들이 더 나은 전략을 가지고 있지만, 위에서 설명한 두 가지 과거 순서 및 위치 동기화 전략을 사용할 수 없습니다. 그러기 위해서는 자체 전략을 동기화 전략과 통합해야 하며, 전략을 전면적으로 개편해야 할 수도 있는데, 이는 시간이 많이 걸리고 힘든 일입니다. 성숙한 전략 중 일부를 주문 동기화 기능으로 직접 업그레이드할 수 있는 좋은 방법이 있나요? 해결책: 이 문서에서 설계된 시스템인 주문 동기화 템플릿 라이브러리를 설계할 수 있습니다.订单同步管理系统类库(Single Server)전략)을 사용하면 참조 계정 소유자(주문을 받는 사람)가 이 템플릿 라이브러리를 자신의 전략에 직접 내장하여 주문 및 위치 동기화 기능을 구현할 수 있습니다.
    1. 추가 실제 주문 하나를 줄이세요. 마지막으로, 위에서 설명한 두 가지 과거 순서와 위치 동기화 전략을 사용할 경우의 문제점입니다. 참조계좌(단일계좌)의 포지션을 모니터링하기 위해 추가 실계좌를 개설해야 합니다. 해결책: 템플릿 라이브러리를 사용하여 참조 계정 전략에 기능을 내장하세요.

따라서 이 시스템은 2가지 부분으로 구성됩니다. 1. 주문 동기화 관리 시스템 클래스 라이브러리(단일 서버) 2. 동기 관리 시스템(Synchronous Server) 주문

이제 요구 사항이 명확해졌으니 디자인을 시작해 보겠습니다!

디자인 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)

주문 동기화 관리 시스템(Synchronous Server)이 다음 신호를 수신했습니다. 우리는 아직 주문 동기화 관리 시스템(Synchronous Server)의 설계를 완료하지 않았습니다. 먼저 거래를 하지 않고 신호만 출력하는 간단한 코드로 구현해 보겠습니다.

주문 동기화 관리 시스템(Synchronous Server)의 임시 코드:

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)이건 임시 코드일 뿐이고, 다음 호에서 이 코드의 디자인에 대해 계속 살펴보겠습니다.