Изобретатели количественно оценивают новые возможности: используя_Функция Serve легко создает HTTP-сервисы

Создано: 2024-11-12 22:10:49, Обновлено: 2024-11-15 09:27:20
comments   0
hits   210

[TOC]

Изобретатели количественно оценивают новые возможности: используя_Функция Serve легко создает HTTP-сервисы

В количественной торговле и автоматизированной разработке стратегий иногда используются http-сервисы. Недавно количественная платформа Inventor добавила_Serve() Функция, предоставляющая пользователям гибкие возможности создания сервисов HTTP, HTTPS и TCP. Благодаря этой функции разработчики могут упростить процесс настройки сервисов и реализовать больше индивидуальных сервисов в количественной среде, что делает разработку стратегии более плавной и удобной. В этой статье будет рассмотрено_Serve() Сценарии использования и основные операции функции помогут вам быстро приступить к работе с этой новой функцией Inventor Quant.

о_Serve()Обновление в документации API платформы:

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


нуждаться

Платформа была модернизирована_Serve()функция (поскольку язык JavaScript ранее не имел функции создания сервисов, эта функция поддерживает только политики языка JavaScript). Проще говоря, она позволяет политикам иметь возможность создавать сетевые сервисы. На основе этой функции мы можем разработать множество функций и решить множество потребностей. Например, стратегия может иметь внешние интерфейсы, пересылку данных и взаимодействовать с общими функциями протокола платформы для бесшовной инкапсуляции обменов, которые не поддерживаются платформой FMZ.

В этой статье мы в качестве примера возьмем требование «взаимодействия с функцией общего протокола платформы для бесшовной инкапсуляции обменов, которые не поддерживаются платформой FMZ». В предыдущих статьях「Общее руководство по протоколу」В этой статье мы используем язык Python для инкапсуляции API биржи OKX в спотовом режиме (поскольку сама FMZ поддерживает OKX, OKX используется здесь просто в качестве примера, и он применим к другим биржам, к которым платформа FMZ не подключена). Программа общего протокола Python в этой статье должна быть запущена отдельно. Когда язык JavaScript поддерживает_Serve()После функции становится проще получить доступ к общему протоколу стратегии языка JavaScript.

Мы инкапсулируем общий протокол интерфейса обмена в «библиотеку шаблонов» и напрямую интегрируем его в стратегию, чтобы стратегия могла беспрепятственно получать доступ к обменам, которые не поддерживаются в FMZ. Я не буду здесь вдаваться в подробности настройки объекта обмена «Общий протокол», вы можете обратиться к статье:

https://www.fmz.com/digest-topic/10518

  • Общая конфигурация обмена протоколами на платформе выглядит следующим образом:

Изобретатели количественно оценивают новые возможности: используя_Функция Serve легко создает HTTP-сервисы

При разработке шаблона вы можете/OKXОпределяет, к какому обмену принадлежит настроенный объект обмена общим протоколом.


Реализация шаблона общего протокола

Сначала создайте новую стратегию в платформе количественной торговли Inventor, установите тип стратегии на «Библиотека шаблонов», а язык стратегии на JavaScript.

Разработка параметров шаблона

Добавьте три параметра в созданный шаблон политики:

Изобретатели количественно оценивают новые возможности: используя_Функция Serve легко создает HTTP-сервисы

Затем вы можете приступить к проектированию и написанию кода для общего шаблона протокола.

Реализация кода

Код написан в стиле TS.$.startService()Функция представляет собой функцию интерфейса шаблона, используемую для запуска службы общего протокола.

// @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("启动通用协议服务,address:", address, ",port:", port, "#FF0000")
    if (proxyConfig != "") {
        Log("设置代理:", proxyConfig, "#FF0000")
    }
}

Из-за ограниченного пространства здесь реализованы не все интерфейсы, а толькоЗапрос рынкаЗапрос активов、*IO-вызовы*Заинтересованные студенты могут реализовать все интерфейсы; после завершения проектирования сохраните код шаблона и сохраните имя шаблона как: «Пример общего протокола версии TypeScript».


Стратегия тестирования

После настройки API-ключа, секретного ключа, парольной фразы и т. д. биржи OKX мы можем написать тестовую стратегию для тестирования.

Стратегия Проверьте нашу разработанную библиотеку шаблонов:

Изобретатели количественно оценивают новые возможности: используя_Функция Serve легко создает HTTP-сервисы

Код стратегии тестирования:

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

    // 测试GetAccount
    Log(`exchange.GetAccount():`, exchange.GetAccount())

    // 测试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"))

    // 输出通用协议添加的交易所名称
    Log(`exchange.GetName():`, exchange.GetName())

    // 输出通用协议添加的交易所标签
    Log(`exchange.GetLabel():`, exchange.GetLabel())
}

Проведение тестов

Изобретатели количественно оценивают новые возможности: используя_Функция Serve легко создает HTTP-сервисы

Как видите, стратегии достаточно проверить шаблон, чтобы добиться бесперебойного доступа к бирже OKX (хотя биржа OKX уже поддерживает это, например, здесь OKX заменяется биржей, к которой FMZ еще не подключен).