En la carga de los recursos... Cargando...

Nueva característica de FMZ Quant: Utilice la función _Serve para crear servicios HTTP fácilmente

El autor:FMZ~Lydia, Creado: 2024-11-13 11:59:30, Actualizado: 2024-11-15 09:57:05

img

En el comercio cuantitativo y el desarrollo de estrategias automatizadas, a veces se utilizan servicios http._Serve()Con esta función, los desarrolladores pueden simplificar el proceso de configuración del servicio e implementar servicios más personalizados en el entorno cuantitativo, haciendo que el diseño de estrategias sea más suave y conveniente._Serve()para ayudarle a comenzar rápidamente con esta nueva función de FMZ Quant.

La documentación de la API de la plataforma para_Serve()se ha actualizado:

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

Demandas

La plataforma ha actualizado la función_Serve()(ya que el lenguaje JavaScript no tenía la función de crear servicios antes, esta función solo admite estrategias en el lenguaje JavaScript). En pocas palabras, permite que las estrategias tengan la capacidad de crear servicios de red. Basándose en esta función, podemos desarrollar muchas funciones y resolver muchos problemas. Por ejemplo, las estrategias pueden tener interfaces externas, reenvío de datos y cooperar con la función de protocolo personalizado de la plataforma para encapsular sin problemas los intercambios que no son compatibles con la plataforma FMZ.

En este artículo, utilizaremos la demanda de cooperar con la función de protocolo personalizado de la plataforma para encapsular sin problemas los intercambios que no son compatibles con la plataforma FMZ como ejemplo.Guía del protocolo personalizado, utilizamos el lenguaje Python para encapsular la API del intercambio OKX en modo spot (porque el propio FMZ admite OKX, OKX se utiliza aquí solo como ejemplo, y es aplicable a otros intercambios que no están conectados a la plataforma FMZ)._Serve(), es más fácil para la estrategia del lenguaje JavaScript acceder al protocolo personalizado.

Encapsulamos el protocolo personalizado de la interfaz de intercambio para ser encapsulado como una biblioteca de plantillas e integrarlo directamente en la estrategia, para que la estrategia pueda acceder sin problemas a los intercambios que no son compatibles con FMZ. No entraré en detalles sobre cómo configurar el objeto de intercambio Protocolo personalizado aquí, puede consultar el artículo:

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

  • La configuración de intercambio de protocolos personalizados en la plataforma es la siguiente:

img

Al diseñar una plantilla, podemos usar/OKXpara identificar a qué intercambio pertenece el objeto de intercambio de protocolos personalizados configurado.

Implementación de plantillas de protocolo personalizadas

En primer lugar, crear una nueva estrategia en la plataforma de negociación FMZ Quant, establecer el tipo de estrategia a la biblioteca de plantillas, y el lenguaje de estrategia a JavaScript.

Diseño de parámetros de plantilla

Añadir 3 parámetros a la plantilla de estrategia creada:

img

Luego podemos empezar a diseñar y escribir código para la plantilla de protocolo personalizado.

Implementación del código

El código está escrito en estilo TS.$.startService()Función es una función de interfaz de plantilla utilizada para iniciar el servicio de protocolo personalizado.

// @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")
    }
}

Debido al espacio limitado, no todas las interfaces se implementan aquí.consultas de mercado , búsqueda de activos, yLlamada de IOLos usuarios que estén interesados pueden implementar todas las interfaces. Después de que el diseño esté completo, guarde el código de la plantilla y guarde el nombre de la plantilla como: TypeScript versión ejemplo de protocolo personalizado.

Estrategia de pruebas

Después de configurar el OKX exchanges apikey, secretkey, contraseña, etc, podemos escribir una estrategia de prueba para probar.

Estrategia comprueba nuestra biblioteca de plantillas diseñada:

img

Código de la estrategia de ensayo:

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())
}

Pruebas de ejecución

img

Como podemos ver, la estrategia solo necesita verificar una plantilla para lograr un acceso sin problemas al intercambio OKX (aunque el intercambio OKX ya lo admite, por ejemplo, OKX se reemplaza aquí por un intercambio al que FMZ aún no se ha conectado).


Más.