양적 거래 및 자동 전략 개발에서 http 서비스는 때때로 사용됩니다. FMZ 양적 거래 플랫폼은 기능을 추가했습니다._Serve()
최근, 사용자들에게 유연한 HTTP, HTTPS 및 TCP 서비스 생성 기능을 제공합니다. 이 기능을 통해 개발자는 서비스 구성 프로세스를 단순화하고 양적 환경에서 더 사용자 정의 된 서비스를 구현하여 전략 설계가 더 원활하고 편리합니다. 이 문서에서는 기능의 사용 시나리오와 기본 작업을 소개합니다._Serve()
FMZ Quant의 새로운 기능으로 빠르게 시작할 수 있도록 도와드립니다.
플랫폼 API 문서_Serve()
업데이트 되었습니다.
플랫폼은 기능을 업그레이드했습니다_Serve()
(자바스크립트 언어는 이전에 서비스를 만드는 기능을 가지고 있지 않았기 때문에 이 기능은 자바스크립트 언어의 전략만을 지원합니다.) 간단히 말해서, 이것은 전략이 네트워크 서비스를 만들 수있는 능력을 가질 수 있도록합니다. 이 기능을 기반으로 우리는 많은 기능을 개발하고 많은 문제를 해결할 수 있습니다. 예를 들어, 전략은 외부 인터페이스, 데이터 전송을 가질 수 있으며 FMZ 플랫폼에서 지원되지 않는 교환을 원활하게 캡슐화하기 위해 플랫폼의 사용자 지정 프로토콜 기능과 협력 할 수 있습니다.
이 문서에서는, 우리는 예를 들어 FMZ 플랫폼에서 지원되지 않는 교환을 원활하게 캡슐화하기 위해 플랫폼의 사용자 지정 프로토콜 기능과 협력하는 요구를 사용합니다. 이전 기사에서사용자 지정 프로토콜 가이드, 우리는 파이썬 언어를 사용하여 SPOT 모드에서 OKX 교환의 API를 캡슐화했습니다. (FMZ 자체는 OKX를 지원하기 때문에, OKX는 여기에 예로 사용되며 FMZ 플랫폼에 연결되지 않은 다른 교환에 적용됩니다.) 이 문서에서 파이썬의 사용자 지정 프로토콜 프로그램은 별도로 실행되어야합니다. 자바스크립트 언어가 함수를 지원 할 때_Serve()
, 자바스크립트 언어 전략이 사용자 지정 프로토콜에 접근하는 것이 더 쉽습니다.
우리는 교환 인터페이스의 사용자 지정 프로토콜을
템플릿을 디자인할 때/OKX
구성된 사용자 지정 프로토콜 교환 객체가 어느 교환에 속하는지 확인하기 위해서입니다.
먼저, FMZ 양자 거래 플랫폼에서 새로운 전략을 만들고, 전략 유형을 템플릿 라이브러리로 설정하고, 전략 언어를 자바스크립트로 설정합니다.
생성된 전략 템플릿에 3개의 매개 변수를 추가합니다.
그러면 우리는 사용자 지정 프로토콜 템플릿을 설계하고 코드를 작성할 수 있습니다.
코드는 TS 스타일로 작성되었습니다.$.startService()
function는 사용자 지정 프로토콜 서비스를 시작하는데 사용되는 템플릿 인터페이스 함수입니다.
// @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")
}
}
제한된 공간으로 인해 모든 인터페이스가 여기에 구현되지 않습니다.시장 조회 , 자산 질의, 그리고IO 호출모든 인터페이스를 구현할 수 있습니다. 디자인이 완료되면 템플릿 코드를 저장하고 템플릿 이름을 저장합니다:
OKX 교환의 apikey, secretkey, 암호 문장 등을 구성한 후 테스트 전략을 작성할 수 있습니다.
전략은 설계된 템플릿 라이브러리를 검사합니다.
테스트 전략 코드:
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())
}
우리가 볼 수 있듯이, 전략은 OKX 교환에 원활한 액세스를 달성하기 위해 템플릿을 확인하는 것 만 (OKX 교환이 이미 지원하지만, 예를 들어, OKX는 FMZ가 아직 연결하지 않은 교환으로 대체됩니다.)