Die Ressourcen sind geladen. Beförderung...

Neue Funktion von FMZ Quant: _Serve-Funktion zum einfachen Erstellen von HTTP-Diensten

Schriftsteller:FMZ~Lydia, Erstellt: 2024-11-13 11:59:30, aktualisiert: 2024-11-15 09:57:05

img

Bei quantitativen Handel und automatisierter Strategieentwicklung werden manchmal http-Dienste eingesetzt._Serve()Diese Funktion ermöglicht es Entwicklern, den Service-Konfigurationsprozess zu vereinfachen und mehr kundenspezifische Services in der quantitativen Umgebung zu implementieren, wodurch die Strategieentwicklung reibungsloser und komfortabler wird._Serve()um Ihnen zu helfen, schnell mit dieser neuen Funktion von FMZ Quant zu beginnen.

Die Plattform-API-Dokumentation für_Serve()wurde aktualisiert:

https://www.fmz.com/syntax-guide/fun/global/__serve

Nachfrage

Die Plattform hat die Funktion aktualisiert_Serve()(Da die JavaScript-Sprache zuvor nicht die Funktion hatte, Dienste zu erstellen, unterstützt diese Funktion nur Strategien in der JavaScript-Sprache). Einfach ausgedrückt, ermöglicht sie Strategien, Netzwerkdienste zu erstellen. Basierend auf dieser Funktion können wir viele Funktionen entwickeln und viele Probleme lösen. Strategien können beispielsweise externe Schnittstellen haben, Daten weiterleiten und mit der benutzerdefinierten Protokollfunktion der Plattform zusammenarbeiten, um Austausch, die von der FMZ-Plattform nicht unterstützt werden, nahtlos zu verkapseln.

In diesem Artikel verwenden wir die Anforderung mit der benutzerdefinierten Protokollfunktion der Plattform zusammenzuarbeiten, um Austausch, die von der FMZ-Plattform nicht unterstützt werden, nahtlos zu verkapseln als Beispiel.Benutzerdefiniertes Protokollführer, haben wir Python-Sprache verwendet, um die API von OKX-Austausch im Spot-Modus zu verkapseln (weil FMZ selbst OKX unterstützt, wird OKX hier nur als Beispiel verwendet, und es ist anwendbar auf andere Austausch, die nicht mit der FMZ-Plattform verbunden sind)._Serve(), ist es für die JavaScript-Sprachstrategie einfacher, auf das benutzerdefinierte Protokoll zuzugreifen.

Wir verkapseln das benutzerdefinierte Protokoll der Austauschoberfläche, um es als Template-Bibliothek zu verkapseln und direkt in die Strategie zu integrieren, so dass die Strategie nahtlos auf Exchange zugreifen kann, die nicht auf FMZ unterstützt werden.

https://www.fmz.com/bbs-topic/10527

  • Die benutzerdefinierte Protokoll-Austauschkonfiguration auf der Plattform ist wie folgt:

img

Bei der Gestaltung einer Vorlage können wir/OKXum festzustellen, zu welcher Börse das konfigurierte benutzerdefinierte Protokoll-Austauschobjekt gehört.

Implementierung einer benutzerdefinierten Protokollvorlage

Erstens, erstellen Sie eine neue Strategie in der FMZ Quant Trading Platform, setzen Sie den Strategie-Typ auf Template-Bibliothek und die Strategie-Sprache auf JavaScript.

Entwurf der Vorlageparameter

Hinzufügen von 3 Parametern zur erstellten Strategievorlage:

img

Dann können wir anfangen, Code für die benutzerdefinierte Protokollvorlage zu entwerfen und zu schreiben.

Umsetzung des Codes

Der Code ist im TS-Stil geschrieben.$.startService()Funktion ist eine Schablonenoberflächenfunktion, mit der der benutzerdefinierte Protokolldienst gestartet wird.

// @ts-check

$.startService = function (address, port, proxyConfig) {
    __Serve(`http://${address}:${port}`, function (ctx, proxyConfig) {
        // interface
        interface IData {
            data: object
            raw: object
        }
        
        interface IError {
            error: any
        }
        
        // custom protocol for OKX
        class CustomProtocolOKX {
            apiBase: string = "https://www.okx.com"
            accessKey: string
            secretKey: string
            passphrase: string
            proxyConfig: string = ""
            simulate: boolean = false
            
            constructor(accessKey: string, secretKey: string, passphrase: string, simulate?: boolean, proxyConfig?: string) {
                this.accessKey = accessKey
                this.secretKey = secretKey
                this.passphrase = passphrase
                if (typeof(simulate) == "boolean") {
                    this.simulate = simulate
                }
                this.proxyConfig = proxyConfig
            }
            
            httpReq(method: string, path: string, query: string = "", params: {[key: string]: any} = {}, headers: {key: string, value: string | ArrayBuffer}[] = []): {[key: string]: any} {
                let ret = null
                let options = {
                    method: method,                    
                    headers: {
                        'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6', 
                        'Content-Type': 'application/json; charset=UTF-8',
                        'x-simulated-trading': this.simulate ? "1" : "0"
                    },
                }
                
                // headers
                if (Array.isArray(headers) && headers.length > 0) {
                    for (var pair of headers) {
                        options.headers[pair.key] = pair.value
                    }
                }
                
                let url = ""
                if (method == "GET") {
                    if (typeof(query) == "string" && query.length > 0) {
                        url = `${this.apiBase}${path}?${query}`
                    } else {
                        url = `${this.apiBase}${path}`
                    }
                } else {
                    url = `${this.apiBase}${path}`
                    options.body = JSON.stringify(params)
                }

                // request
                try {
                    if (this.proxyConfig != "") {
                        url = `${this.proxyConfig}${url}`
                    }
                    ret = JSON.parse(HttpQuery(url, options))
                } catch(e) {
                    return null
                }
    
                return ret
            }

            callSignedAPI(method: string, path: string, query: string = "", params: {[key: string]: any} = {}): {[key: string]: any} {
                const strTime = new Date().toISOString().slice(0, -5) + 'Z'
                let jsonStr = ""
                if (method == "GET") {
                    jsonStr = Object.keys(params).length > 0 ? JSON.stringify(params) : ""
                } else {
                    jsonStr = Object.keys(params).length > 0 ? JSON.stringify(params) : "{}"
                }                
                let message = `${strTime}${method}${path}${jsonStr}`
                if (method === "GET" && query !== "") {
                    message = `${strTime}${method}${path}?${query}${jsonStr}`
                }

                const signature = Encode("sha256", "string", "base64", message, "string", this.secretKey)
                let headers = []
                headers.push({key: "OK-ACCESS-KEY", value: this.accessKey})
                headers.push({key: "OK-ACCESS-PASSPHRASE", value: this.passphrase})
                headers.push({key: "OK-ACCESS-TIMESTAMP", value: strTime})
                headers.push({key: "OK-ACCESS-SIGN", value: signature})                

                return this.httpReq(method, path, query, params, headers)
            }
            
            urlEncode(params: {[key: string]: string | number}): string {
                let encodeParams: string[] = []
                for (const [key, value] of Object.entries(params)) {
                    encodeParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
                }

                return encodeParams.join("&")
            }

            symbol2Inst(symbol: string): string {
                let arr = symbol.split("_")
                if (arr.length >= 2) {
                    return `${arr[0]}-${arr[1]}`.toUpperCase()
                } else {
                    return `${arr[0]}-USDT`.toUpperCase()
                }
            }

            getSymbol(inst: string): string {
                let arr = inst.split("-")
                if (arr.length >= 2) {
                    return `${arr[0]}_${arr[1]}`.toUpperCase()
                } else {
                    return `${arr[0]}-USDT`.toUpperCase()
                }
            }
            
            // The following code encapsulates OKX's interface
            GetTicker(symbol: string): IData | IError {
                // GET /api/v5/market/ticker , param: instId 

                let inst = this.symbol2Inst(symbol)
                let ret = this.httpReq("GET", "/api/v5/market/ticker", `instId=${inst}`)

                let retData = {}
                for (var ele of ret["data"]) {
                    retData["symbol"] = this.getSymbol(ele["instId"])
                    retData["buy"] = ele["bidPx"]
                    retData["sell"] = ele["askPx"]
                    retData["high"] = ele["high24h"]
                    retData["low"] = ele["low24h"]
                    retData["open"] = ele["open24h"]
                    retData["last"] = ele["last"]
                    retData["vol"] = ele["vol24h"]
                    retData["time"] = ele["ts"]
                }

                return {data: retData, raw: ret}
            }
            
            GetAccount(): IData | IError {
                // GET /api/v5/account/balance
                
                let ret = this.callSignedAPI("GET", "/api/v5/account/balance")

                let retData = []
                for (var ele of ret["data"]) {
                    for (var detail of ele["details"]) {
                        let asset = {"currency": detail["ccy"], "free": detail["availEq"], "frozen": detail["ordFrozen"]}
                        if (detail["availEq"] == "") {
                            asset["free"] = detail["availBal"]
                        }
                        retData.push(asset)
                    }
                }
                
                return {data: retData, raw: ret}
            }

            IO(method: string, path: string, params: {[key: string]: any}): {[key: string]: any} {
                let ret = null 
                if (method == "GET") {
                    ret = this.callSignedAPI(method, path, this.urlEncode(params))
                } else {
                    ret = this.callSignedAPI(method, path, "", params)
                }

                return {data: ret}
            }
        }
        
        // protocol factory
        class ProtocolFactory {
            static createExWrapper(accessKey: string, secretKey: string, exName: string): any {
                let protocol = null
                if (exName == "/OKX") {
                    try {
                        let passphrase = ""
                        let simulate = false
                        let arrSecretKey = secretKey.split(",")

                        if (arrSecretKey.length == 2) {
                            secretKey = arrSecretKey[0]
                            passphrase = arrSecretKey[1]
                        } else if (arrSecretKey.length == 3) {
                            secretKey = arrSecretKey[0]
                            passphrase = arrSecretKey[1]
                            simulate = arrSecretKey[2] == "simulate" ? true : false 
                        } else {
                            return null
                        }
                        protocol = new CustomProtocolOKX(accessKey, secretKey, passphrase, simulate, proxyConfig)
                    } catch(e) {
                        Log("e.name:", e.name, "e.stack:", e.stack, "e.message:", e.message)
                        return null
                    }
                }
                return protocol
            }
        }
        
        // http service
        let resp = {}
        let reqMethod = ctx.method()
        let reqPath = ctx.path()
        let httpMethod = ctx.header("Http-Method")
        let reqBody = null

        try {
            reqBody = JSON.parse(ctx.body())
        } catch(e) {
            resp = {error: {name: e.name, stack: e.stack, message: e.message, errDesc: "JSON parse error."}}
        }

        // onPost
        if (reqMethod == "POST") {
            if (!["access_key", "secret_key", "method", "params"].every(key=> key in reqBody)) {
                resp = {error: {reqBody: reqBody, errDesc: "reqBody error."}}
            }

            if ("error" in resp) {
                ctx.write(JSON.stringify(resp))
                return 
            }

            let accessKey = reqBody["access_key"]
            let secretKey = reqBody["secret_key"]
            let method = reqBody["method"]
            let params = reqBody["params"]

            let protocol = ProtocolFactory.createExWrapper(accessKey, secretKey, reqPath)
            if (!protocol) {
                ctx.write(JSON.stringify({error: {errDesc: "createExWrapper error."}}))
                return 
            }

            // process GetTicker / GetAccount ...
            if (method == "ticker") {
                if (!["symbol"].every(key=> key in params)) {
                    resp = {error: {params: params, errDesc: "params error."}}
                } else {
                    let symbol = params["symbol"]
                    resp = protocol.GetTicker(symbol)
                }
            } else if (method == "accounts") {
                resp = protocol.GetAccount()
            } else if (method.slice(0, 6) == "__api_") {
                resp = protocol.IO(httpMethod, method.slice(6), params)
            } else {
                ctx.write(JSON.stringify({error: {method: method, errDesc: "method not support."}}))
                return 
            }

            ctx.write(JSON.stringify(resp))
        }
    }, proxyConfig)
}

function init() {
    $.startService(address, port, proxyConfig)
    Log("Start the custom protocol service, address:", address, ",port:", port, "#FF0000")
    if (proxyConfig != "") {
        Log("Setting up the proxy:", proxyConfig, "#FF0000")
    }
}

Aufgrund des begrenzten Platzes werden hier nicht alle Schnittstellen implementiert.Marktforschung , Vermögensanforderung, undIO-AufrufSie können alle Schnittstellen implementieren. Nachdem das Design abgeschlossen ist, speichern Sie den Vorlagencode und den Vorlagennamen als: TypeScript-Version benutzerdefiniertes Protokollbeispiel.

Teststrategie

Nachdem wir den OKX-Austausch s Apikey, Secretkey, Passphrase usw. konfiguriert haben, können wir eine Teststrategie zum Testen schreiben.

Strategie überprüft unsere entworfene Vorlagenbibliothek:

img

Teststrategiecode:

function main() {
    // Test GetTicker
    Log(`exchange.GetTicker():`, exchange.GetTicker())

    // Test GetAccount
    Log(`exchange.GetAccount():`, exchange.GetAccount())

    // Test exchange.IO
    Log(`exchange.IO("api", "POST", "/api/v5/trade/cancel-all-after", "timeOut=0"):`, exchange.IO("api", "POST", "/api/v5/trade/cancel-all-after", "timeOut=0"))

    // Output the exchange name added by the custom protocol
    Log(`exchange.GetName():`, exchange.GetName())

    // Output exchange tags added by the custom protocol
    Log(`exchange.GetLabel():`, exchange.GetLabel())
}

Laufende Prüfungen

img

Wie wir sehen können, muss die Strategie nur eine Vorlage überprüfen, um nahtlosen Zugriff auf die OKX-Börse zu erhalten (obwohl die OKX-Börse sie bereits unterstützt, zum Beispiel wird OKX hier durch eine Börse ersetzt, an die FMZ noch nicht angeschlossen ist).


Mehr