In quantitative trading and automated strategy development, http services are sometimes used. The FMZ Quant Trading Platform has added a function _Serve()
recently, which provides users with flexible HTTP, HTTPS and TCP service creation capabilities. With this function, developers can simplify the service configuration process and implement more customized services in the quantitative environment, making strategy design smoother and more convenient. This article will introduce the usage scenarios and basic operations of the function _Serve()
to help you quickly get started with this new function of FMZ Quant.
The platform API documentation for _Serve()
has been updated:
Demand
The platform has upgraded the function _Serve()
(since JavaScript language did not have the function of creating services before, this function only supports strategies in JavaScript language). Simply put, it enables strategies to have the ability to create network services. Based on this function, we can develop many functions and solve many problems. For example, strategies can have external interfaces, data forwarding, and cooperate with the platform's custom protocol function to seamlessly encapsulate exchanges that are not supported by the FMZ platform.
In this article, we will use the demand of "cooperating with the platform's custom protocol function to seamlessly encapsulate exchanges that are not supported by the FMZ platform" as an example. In the previous article Custom Protocol Guide, we used Python language to encapsulate the API of OKX exchange in spot mode (because FMZ itself supports OKX, OKX is used here just as an example, and it is applicable to other exchanges that are not connected to the FMZ platform). The custom protocol program of Python in this article needs to be run separately. When the JavaScript language supports function _Serve()
, it is easier for the JavaScript language strategy to access the custom protocol.
We encapsulate the custom protocol of the exchange interface to be encapsulated as a "template library" and integrate it into the strategy directly, so that the strategy can seamlessly access exchanges that are not supported on FMZ. I will not go into details about how to configure the "Custom Protocol" exchange object here, you can refer to the article:
- The custom protocol exchange configuration on the platform is as follows:
When designing a template, we can use /OKX
to identify which exchange the configured custom protocol exchange object belongs to.
Custom Protocol Template Implementation
First, create a new strategy in the FMZ Quant Trading Platform, set the strategy type to template library, and the strategy language to JavaScript.
Template Parameter Design
Add 3 parameters to the created strategy template:
Then we can start designing and writing code for the custom protocol template.
Code Implementation
The code is written in TS style. The $.startService()
function is a template interface function used to start the custom protocol service.
// @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") } }
Due to limited space, not all interfaces are implemented here. Only market query , asset query , and IO call are implemented. Users who are interested can implement all interfaces. After the design is completed, save the template code and save the template name as: "TypeScript version custom protocol example".
Testing Strategy
After configuring the OKX exchange's apikey, secretkey, passphrase, etc., we can write a test strategy to test.
Strategy checks our designed template library:
Test strategy code:
functionmain() { // Test GetTickerLog(`exchange.GetTicker():`, exchange.GetTicker()) // Test GetAccountLog(`exchange.GetAccount():`, exchange.GetAccount()) // Test exchange.IOLog(`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 protocolLog(`exchange.GetName():`, exchange.GetName()) // Output exchange tags added by the custom protocolLog(`exchange.GetLabel():`, exchange.GetLabel()) }
Running Tests
As we can see, the strategy only needs to check a template to achieve seamless access to the OKX exchange (although the OKX exchange already supports it, for example, OKX is replaced here with an exchange that FMZ has not yet connected to).