4
집중하다
1111
수행원

발명가들은 새로운 기능을 정량화합니다: 사용_Serve 기능은 HTTP 서비스를 쉽게 생성합니다.

만든 날짜: 2024-11-12 22:10:49, 업데이트 날짜: 2024-11-15 09:27:20
comments   0
hits   275

[TOC]

발명가들은 새로운 기능을 정량화합니다: 사용_Serve 기능은 HTTP 서비스를 쉽게 생성합니다.

양적 거래와 자동화된 전략 개발에서는 http 서비스가 가끔씩 사용됩니다. Inventor Quantitative Platform이 최근 추가되었습니다._Serve() 사용자에게 유연한 HTTP, HTTPS 및 TCP 서비스 생성 기능을 제공하는 기능입니다. 이 기능을 사용하면 개발자는 서비스 구성 프로세스를 간소화하고, 정량적 환경에서 보다 맞춤화된 서비스를 구현할 수 있으며, 전략 설계가 보다 원활하고 편리해집니다. 이 기사에서는 다음 내용을 다룰 것입니다._Serve() 이 기능의 사용 시나리오와 기본 작업은 Inventor Quant의 새로운 기능을 빠르게 시작하는 데 도움이 됩니다.

~에 대한_Serve()플랫폼 API 문서가 업데이트되었습니다.

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


필요

플랫폼이 업그레이드 되었습니다._Serve()기능(JavaScript 언어에는 이전에 서비스를 만드는 기능이 없었기 때문에 이 기능은 JavaScript 언어 정책만 지원합니다). 간단히 말해서, 정책이 네트워크 서비스를 만들 수 있는 기능을 갖도록 합니다. 이런 기능을 바탕으로 우리는 다양한 기능을 개발하고, 다양한 요구 사항을 해결할 수 있습니다. 예를 들어, 이 전략에는 외부 인터페이스, 데이터 전달 기능이 포함될 수 있으며, 플랫폼의 일반 프로토콜 기능과 협력하여 FMZ 플랫폼에서 지원하지 않는 거래도 원활하게 캡슐화할 수 있습니다.

이 글에서는 “FMZ 플랫폼에서 지원하지 않는 거래소를 원활하게 캡슐화하기 위해 플랫폼의 일반 프로토콜 기능과 협력해야 한다”는 요구 사항을 예로 들어보겠습니다. 이전 기사에서「일반 프로토콜 가이드」이 글에서는 Python 언어를 사용하여 스팟 모드에서 OKX 거래소의 API를 캡슐화합니다(FMZ 자체가 OKX를 지원하므로, 여기서는 OKX를 단지 예시로 사용하고, FMZ 플랫폼에 연결되지 않은 다른 거래소에도 적용할 수 있습니다). 이 문서의 Python 일반 프로토콜 프로그램은 별도로 실행해야 합니다. JavaScript 언어가 지원하는 경우_Serve()함수를 사용하면 JavaScript 언어 전략의 공통 프로토콜에 쉽게 접근할 수 있습니다.

우리는 거래소 인터페이스의 일반 프로토콜을 “템플릿 라이브러리”에 캡슐화하여 전략에 직접 통합함으로써 전략이 FMZ에서 지원되지 않는 거래소에 원활하게 액세스할 수 있도록 합니다. 여기서는 “일반 프로토콜” 교환 객체를 구성하는 방법에 대한 세부 사항은 설명하지 않겠습니다. 다음 문서를 참조할 수 있습니다.

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

  • 플랫폼의 일반적인 프로토콜 교환 구성은 다음과 같습니다.

발명가들은 새로운 기능을 정량화합니다: 사용_Serve 기능은 HTTP 서비스를 쉽게 생성합니다.

템플릿을 디자인할 때 다음을 수행할 수 있습니다./OKX구성된 일반 프로토콜 교환 개체가 속한 교환을 식별합니다.


일반 프로토콜 템플릿 구현

먼저, Inventor Quantitative Trading Platform에서 새로운 전략을 만들고, 전략 유형을 템플릿 라이브러리로, 전략 언어를 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 버전 일반 프로토콜 예제”로 저장합니다.


테스트 전략

OKX 거래소의 apikey, secretkey, passphrase 등을 구성한 후 테스트 전략을 작성하여 테스트할 수 있습니다.

전략 디자인된 템플릿 라이브러리를 확인하세요:

발명가들은 새로운 기능을 정량화합니다: 사용_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가 아직 연결하지 않은 거래소로 대체되었습니다).