[TOC]
Trong giao dịch định lượng và phát triển chiến lược tự động, đôi khi dịch vụ http được sử dụng. Nền tảng định lượng Inventor gần đây đã được bổ sung_Serve()
Chức năng 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 tính năng này, các nhà phát triển có thể đơn giản hóa quy trình cấu hình dịch vụ và triển khai nhiều dịch vụ tùy chỉnh hơn trong môi trường định lượng, giúp việc thiết kế chiến lược trở nên dễ dàng và thuận tiện hơn. Bài viết này sẽ giới thiệu_Serve()
Các tình huống sử dụng và thao tác cơ bản của chức năng giúp bạn nhanh chóng bắt đầu sử dụng chức năng mới này của Inventor Quant.
Về_Serve()
Đã cập nhật trong tài liệu API của Nền tảng:
Nền tảng đã được nâng cấp_Serve()
chức năng (vì ngôn ngữ JavaScript trước đây không có chức năng tạo dịch vụ nên chức năng này chỉ hỗ trợ các chính sách ngôn ngữ JavaScript). Nói một cách đơn giản, nó cho phép các chính sách có khả năng tạo 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 nhu cầu. Ví dụ, 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 các chức năng giao thức chung 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ẽ lấy yêu cầu “hợp tác với chức năng giao thức chung 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ợ” làm ví dụ. Trong các bài viết trước「Hướng dẫn giao thức chung」Trong bài viết này, chúng tôi sử dụng ngôn ngữ Python để đóng gói API của sàn giao dịch OKX ở chế độ giao ngay (vì bản thân FMZ hỗ trợ OKX nên OKX được sử dụng ở đây chỉ là ví dụ và có thể áp dụng cho các sàn giao dịch khác mà nền tảng FMZ không kết nối tới). Chương trình giao thức chung Python trong bài viết này cần phải được chạy riêng. Khi ngôn ngữ JavaScript hỗ trợ_Serve()
Sau hàm này, việc truy cập vào giao thức chung của chiến lược ngôn ngữ JavaScript trở nên dễ dàng hơn.
Chúng tôi đóng gói giao thức chung của giao diện trao đổi để được đóng gói vào “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 trao đổi không được hỗ trợ trên FMZ. Tôi sẽ không đi sâu vào cách cấu hình đối tượng trao đổi “General Protocol” ở đây, bạn có thể tham khảo bài viết:
Khi thiết kế mẫu, bạn có thể/OKX
Xác định đối tượng trao đổi giao thức chung được cấu hình thuộc về sàn giao dịch nào.
Đầu tiên, hãy tạo một chiến lược mới trên Nền tảng giao dịch định lượng Inventor, đặt loại chiến lược thành thư viện mẫu và ngôn ngữ chiến lược thành JavaScript.
Thêm ba tham số vào mẫu chính sách đã tạo:
Sau đó, bạn có thể bắt đầu thiết kế và viết mã cho mẫu giao thức chung.
Mã được viết theo phong cách TS.$.startService()
Hàm này là hàm giao diện mẫu được sử dụng để khởi động dịch vụ giao thức chung.
// @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")
}
}
Do không gian hạn chế, không phải tất cả các giao diện đều được triển khai ở đây, chỉ cóTruy vấn thị trường、Truy vấn tài sản、*IO gọi*Sinh viên quan tâm có thể triển khai tất cả các giao diện; sau khi thiết kế hoàn tất, hãy lưu mã mẫu và lưu tên mẫu dưới dạng: “Ví dụ về giao thức chung của phiên bản TypeScript”.
Sau khi cấu hình apikey, secretkey, passphrase, v.v. của sàn giao dịch OKX, chúng ta có thể viế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:
Mã chiến lược thử nghiệm:
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())
}
Như bạn có thể thấy, chiến lược này 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ợ, ví dụ, OKX được thay thế ở đây bằng một sàn giao dịch mà FMZ vẫn chưa kết nối tới).