Tài nguyên đang được tải lên... tải...

Tính năng mới của FMZ Quant: Sử dụng chức năng _Serve để tạo dịch vụ HTTP dễ dàng

Tác giả:FMZ~Lydia, Tạo: 2024-11-13 11:59:30, Cập nhật: 2024-11-15 09:57:05

img

Trong giao dịch định lượng và phát triển chiến lược tự động, các dịch vụ http đôi khi được sử dụng._Serve()gần đây, cung cấp cho người dùng khả năng tạo dịch vụ HTTP, HTTPS và TCP linh hoạt. Với chức năng này, các nhà phát triển có thể đơn giản hóa quá trình cấu hình dịch vụ và thực hiện các dịch vụ tùy chỉnh hơn trong môi trường định lượng, làm cho thiết kế chiến lược mượt mà và thuận tiện hơn. Bài viết này sẽ giới thiệu các kịch bản sử dụng và các hoạt động cơ bản của chức năng_Serve()để giúp bạn nhanh chóng bắt đầu với chức năng mới này của FMZ Quant.

Tài liệu API nền tảng cho_Serve()đã được cập nhật:

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

Nhu cầu

Nền tảng đã nâng cấp chức năng_Serve()(vì ngôn ngữ JavaScript không có chức năng tạo dịch vụ trước đây, chức năng này chỉ hỗ trợ các chiến lược trong ngôn ngữ JavaScript). Nói đơn giản, nó cho phép các chiến lược có khả năng tạo các dịch vụ mạng. Dựa trên chức năng này, chúng ta có thể phát triển nhiều chức năng và giải quyết nhiều vấn đề. Ví dụ, các chiến lược có thể có giao diện bên ngoài, chuyển tiếp dữ liệu và hợp tác với chức năng giao thức tùy chỉnh của nền tảng để đóng gói liền mạch các trao đổi không được nền tảng FMZ hỗ trợ.

Trong bài viết này, chúng tôi sẽ sử dụng nhu cầu hợp tác với chức năng giao thức tùy chỉnh của nền tảng để kết hợp liền mạch các trao đổi không được hỗ trợ bởi nền tảng FMZ như một ví dụ.Hướng dẫn giao thức tùy chỉnh, chúng tôi đã sử dụng ngôn ngữ Python để đóng gói API của OKX trao đổi trong chế độ điểm (vì FMZ chính nó hỗ trợ OKX, OKX được sử dụng ở đây chỉ như một ví dụ, và nó áp dụng cho các trao đổi khác không được kết nối với nền tảng FMZ)._Serve(), nó dễ dàng hơn cho chiến lược ngôn ngữ JavaScript để truy cập vào giao thức tùy chỉnh.

Chúng tôi đóng gói giao thức tùy chỉnh của giao diện trao đổi để được đóng gói dưới dạng thư viện mẫu và tích hợp trực tiếp vào chiến lược, để chiến lược có thể truy cập liền mạch vào các giao dịch không được hỗ trợ trên FMZ. Tôi sẽ không đi sâu vào chi tiết về cách cấu hình đối tượng trao đổi giao thức tùy chỉnh ở đây, bạn có thể tham khảo bài viết:

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

  • Cấu hình trao đổi giao thức tùy chỉnh trên nền tảng là như sau:

img

Khi thiết kế một mẫu, chúng ta có thể sử dụng/OKXđể xác định sàn giao dịch mà đối tượng trao đổi giao thức tùy chỉnh được cấu hình thuộc về.

Thực hiện mẫu giao thức tùy chỉnh

Đầu tiên, tạo một chiến lược mới trong nền tảng giao dịch FMZ Quant, đặt loại chiến lược vào thư viện mẫu và ngôn ngữ chiến lược vào JavaScript.

Thiết kế tham số mẫu

Thêm 3 tham số vào mẫu chiến lược được tạo:

img

Sau đó chúng ta có thể bắt đầu thiết kế và viết mã cho mẫu giao thức tùy chỉnh.

Thực hiện mã

Mã được viết theo phong cách TS.$.startService()function là một chức năng giao diện mẫu được sử dụng để khởi động dịch vụ giao thức tùy chỉnh.

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

Do không gian hạn chế, không phải tất cả các giao diện được thực hiện ở đây.truy vấn thị trường , truy vấn tài sản, vàGọi IONgười dùng quan tâm có thể thực hiện tất cả các giao diện. Sau khi thiết kế được hoàn thành, hãy lưu mã mẫu và lưu tên mẫu như: TypeScript phiên bản protocol tùy chỉnh example.

Chiến lược thử nghiệm

Sau khi cấu hình OKX exchanges apikey, secretkey, passphrase, vv, chúng ta có thể viết một chiến lược thử nghiệm để kiểm tra.

Chiến lược kiểm tra thư viện mẫu được thiết kế của chúng tôi:

img

Mã chiến lược thử nghiệm:

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

Kiểm tra chạy

img

Như chúng ta có thể thấy, chiến lược chỉ cần kiểm tra một mẫu để đạt được quyền truy cập liền mạch vào sàn giao dịch OKX (mặc dù sàn giao dịch OKX đã hỗ trợ nó, ví dụ, OKX được thay thế ở đây bằng một sàn giao dịch mà FMZ chưa kết nối).


Thêm nữa