Chúng tôi FMZ Quant Trading Platform hỗ trợ nhiều sàn giao dịch tiền điện tử và đóng gói các sàn giao dịch chính trên thị trường. Tuy nhiên, vẫn còn nhiều sàn giao dịch không được đóng gói. Đối với người dùng cần sử dụng các sàn giao dịch này, họ có thể truy cập chúng thông qua FMZ Quant Custom Protocol. Không chỉ giới hạn trong sàn giao dịch tiền điện tử, bất kỳ nền tảng nào hỗ trợRESTgiao thức hoặcFIXgiao thức cũng có thể được truy cập.
Bài viết này sẽRESTgiao thức truy cập như một ví dụ để giải thích cách sử dụng giao thức tùy chỉnh của nền tảng giao dịch FMZ Quant để đóng gói và truy cập API của sàn giao dịch OKX. Trừ khi có quy định khác, bài viết này đề cập đến giao thức tùy chỉnh REST.
Trang để cấu hình sàn giao dịch trên nền tảng giao dịch lượng tử FMZ:
https://www.fmz.com/m/platforms/add
Strategy instance running on the docker -> Custom protocol program
.
Ví dụ:http://127.0.0.1:6666/OKX
, chương trình giao thức tùy chỉnh và docker thường chạy trên cùng một thiết bị (server), vì vậy địa chỉ dịch vụ được viết là máy cục bộ (localhost), và cổng có thể được sử dụng như một cổng không bị chiếm đóng bởi hệ thống.Strategy instance running on the docker -> Custom protocol program
.Strategy instance running on the docker -> Custom protocol program
.Ảnh chụp màn hình của cấu hình OKX plug-in được tiết lộ trong bài viết là như sau:
OKX trao đổi thông tin cấu hình khóa bí mật:
accessKey: accesskey123 // accesskey123, these are not actual keys, just for demonstration
secretKey: secretkey123
passphrase: passphrase123
http://127.0.0.1:6666
Chương trình giao thức tùy chỉnh có thể xử lý các đường dẫn cụ thể, chẳng hạn như/OKX
.Khi hàm API nền tảng (FMZ) được gọi trong chiến lược, chương trình giao thức tùy chỉnh sẽ nhận được yêu cầu từ docker. Bạn cũng có thể kiểm tra nó bằng cách sử dụng các công cụ gỡ lỗi của nền tảng, ví dụ:
Trang công cụ gỡ lỗi:
function main() {
return exchange.GetTicker("LTC_USDT")
}
Gọi hàmexchange.GetTicker()
, chương trình giao thức tùy chỉnh nhận được yêu cầu:
POST /OKX HTTP/1.1
{
"access_key":"xxx",
"method":"ticker",
"nonce":1730275031047002000,
"params":{"symbol":"LTC_USDT"},
"secret_key":"xxx"
}
exchange.GetTicker()
, method
làticker
.exchange.GetTicker()
, các thông số liên quan là:{"symbol":"LTC_USDT"}
.Khi chương trình giao thức tùy chỉnh nhận được yêu cầu từ docker, nó có thể lấy thông tin như chức năng API nền tảng (bao gồm thông tin tham số) được yêu cầu bởi chiến lược, khóa trao đổi, v.v. dựa trên thông tin được mang trong yêu cầu.
Dựa trên thông tin này, chương trình giao thức tùy chỉnh có thể truy cập giao diện trao đổi để lấy dữ liệu cần thiết hoặc thực hiện một số hoạt động nhất định.
Thông thường giao diện trao đổi có các phương thức như GET / POST / PUT / DELETE, được chia thành giao diện công cộng và giao diện riêng.
Chương trình giao thức tùy chỉnh nhận dữ liệu phản hồi từ giao diện trao đổi và xử lý thêm nó để xây dựng dữ liệu mong đợi của docker (được mô tả bên dưới).GetTicker
, GetAccount
và các chức năng khác trong thực hiện lớp CustomProtocolOKX trong ví dụ giao thức tùy chỉnh Python.
Khi chương trình giao thức tùy chỉnh truy cập vào giao diện API của trao đổi, thực hiện một số hoạt động nhất định hoặc thu thập một số dữ liệu nhất định, nó cần cung cấp dữ liệu cho docker.
Dữ liệu được cung cấp trở lại cho docker thay đổi tùy theo giao diện được gọi bởi chiến lược, và đầu tiên được chia thành hai loại:
{
"data": null, // "data" can be of any type
"raw": null // "raw" can be of any type
}
dữ liệu: Cấu trúc cụ thể của trường này liên quan đếnmethod
trong yêu cầu được nhận bởi chương trình giao thức tùy chỉnh, và được sử dụng để xây dựng cấu trúc dữ liệu cuối cùng được trả về bởi chức năng API nền tảng FMZ. Tất cả các giao diện sẽ được liệt kê dưới đây.
raw: Trường này có thể được sử dụng để truyền dữ liệu thô của phản hồi giao diện API trao đổi, chẳng hạn như cấu trúc Ticker được trả về bởiexchange.GetTicker()
Các lĩnh vực thông tin của cấu trúc Ticker ghi lại dữ liệu củaraw
lĩnh vực vàdata
Một số chức năng API của nền tảng không cần dữ liệu này.
{
"error": "" // "error" contains an error message as a string
}
lỗi: thông tin lỗi, sẽ được hiển thị trong nhật ký lỗi trong khu vực nhật ký của nền tảng giao dịch trực tiếp (FMZ), công cụ gỡ lỗi và các trang khác.
Hiển thị dữ liệu phản hồi giao thức tùy chỉnh được nhận bởi chương trình chiến lược:
// Tested in the debugging tool of the FMZ platform
function main() {
Log(exchange.GetTicker("USDT")) // The trading pair is incomplete, the BaseCurrency part is missing, and the custom protocol plug-in is required to return an error message: {"error": "..."}
Log(exchange.GetTicker("LTC_USDT"))
}
Trên đây là một quá trình ngắn gọn của chương trình giao thức tùy chỉnh tham gia vào việc truy cập vào API trao đổi (FMZ unpackaged).exchange.GetTicker()
Các chi tiết sau đây sẽ giải thích chi tiết tương tác của tất cả các chức năng API nền tảng chi tiết.
Nền tảng đóng gói các chức năng chung của các sàn giao dịch khác nhau và thống nhất chúng thành một chức năng nhất định, chẳng hạn như chức năng GetTicker, yêu cầu thông tin thị trường hiện tại của một sản phẩm nhất định. Đây là một API mà về cơ bản tất cả các sàn giao dịch đều có. Do đó, khi truy cập giao diện API được đóng gói bởi nền tảng trong một trường hợp chiến lược, docker sẽ gửi yêu cầu đến chương trình plug-in
POST /OKX HTTP/1.1
{
"access_key": "xxx",
"method": "ticker",
"nonce": 1730275031047002000,
"params": {"symbol":"LTC_USDT"},
"secret_key": "xxx"
}
Khi gọi các hàm API được đóng gói trên nền tảng FMZ khác nhau trong chiến lược (như GetTicker), định dạng yêu cầu được docker gửi đến giao thức tùy chỉnh cũng sẽ khác nhau.method
vàparams
. Khi thiết kế một giao thức tùy chỉnh, thực hiện các hoạt động cụ thể theo nội dung của phương pháp. Sau đây là các kịch bản yêu cầu-phản ứng cho tất cả các giao diện.
Ví dụ, cặp giao dịch hiện tại là:ETH_USDT
Dữ liệu mà docker mong đợi giao thức tùy chỉnh sẽ trả lời chủ yếu được viết trong trường dữ liệu, và một trường thô cũng có thể được thêm để ghi lại dữ liệu gốc của giao diện trao đổi.
trường phương pháp:
{"symbol":"ETH_USDT"}
Dữ liệu mà docker mong đợi trong phản hồi giao thức tùy chỉnh:
{
"data": {
"symbol": "ETH_USDT", // Corresponds to the Symbol field in the Ticker structure returned by the GetTicker function
"buy": "2922.18", // ...corresponds to the Buy field
"sell": "2922.19",
"high": "2955",
"low": "2775.15",
"open": "2787.72",
"last": "2922.18",
"vol": "249400.888156",
"time": "1731028903911"
},
"raw": {} // A raw field can be added to record the raw data of the exchange API interface response
}
trường phương pháp:
{"limit":"30","symbol":"ETH_USDT"}
Dữ liệu mà docker mong đợi trong phản hồi giao thức tùy chỉnh:
{
"data" : {
"time" : 1500793319499,
"asks" : [
[1000, 0.5], [1001, 0.23], [1004, 2.1]
// ...
],
"bids" : [
[999, 0.25], [998, 0.8], [995, 1.4]
// ...
]
}
}
trường phương pháp:
{"symbol":"eth_usdt"}
Dữ liệu mà docker mong đợi trong phản hồi giao thức tùy chỉnh:
{
"data": [
{
"id": 12232153,
"time" : 1529919412968,
"price": 1000,
"amount": 0.5,
"type": "buy", // "buy"、"sell"、"bid"、"ask"
}, {
"id": 12545664,
"time" : 1529919412900,
"price": 1001,
"amount": 1,
"type": "sell",
}
// ...
]
}
trường phương pháp:
{
"limit":"500",
"period":"60", // 60 minutes
"symbol":"ETH_USDT"
}
Dữ liệu mà docker mong đợi trong phản hồi giao thức tùy chỉnh:
{
"data": [
// "Time":1500793319000,"Open":1.1,"High":2.2,"Low":3.3,"Close":4.4,"Volume":5.5
[1500793319, 1.1, 2.2, 3.3, 4.4, 5.5],
[1500793259, 1.01, 2.02, 3.03, 4.04, 5.05],
// ...
]
}
trường phương pháp:
{}
Dữ liệu mà docker mong đợi trong phản hồi giao thức tùy chỉnh:
{}
trường phương pháp:
{}
Dữ liệu mà docker mong đợi trong phản hồi giao thức tùy chỉnh:
{}
trường phương thức:
{}
Dữ liệu mà docker mong đợi trong phản hồi giao thức tùy chỉnh:
{
"data": [
{"currency": "TUSD", "free": "3000", "frozen": "0"},
{"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"},
// ...
]
}
trường phương pháp:
{}
Dữ liệu mà docker mong đợi trong phản hồi giao thức tùy chỉnh:
{
"data": [
{"currency": "TUSD", "free": "3000", "frozen": "0"},
{"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"},
// ...
]
}
trường phương pháp:
{"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
Dữ liệu mà docker mong đợi trong phản hồi giao thức tùy chỉnh:
{
"data": {
"id": "BTC-USDT,123456"
}
}
trường phương pháp:
{"symbol":"ETH_USDT"}
Dữ liệu mà docker mong đợi trong phản hồi giao thức tùy chỉnh:
{
"data": [
{
"id": "ETH-USDT,123456",
"symbol": "ETH_USDT",
"amount": 0.25,
"price": 1005,
"deal_amount": 0,
"avg_price": "1000",
"type": "buy", // "buy"、"sell"
"status": "pending", // "pending", "pre-submitted", "submitting", "submitted", "partial-filled"
},
// ...
]
}
trường phương pháp:
{
"id":"ETH-USDT,123456", // Calling in the strategy: exchange.GetOrder("ETH-USDT,123456")
"symbol":"ETH_USDT"
}
Dữ liệu mà docker mong đợi trong phản hồi giao thức tùy chỉnh:
{
"data": {
"id": "ETH-USDT,123456",
"symbol": "ETH_USDT"
"amount": 0.15,
"price": 1002,
"status": "pending", // "pending", "pre-submitted", "submitting", "submitted", "partial-filled", "filled", "closed", "finished", "partial-canceled", "canceled"
"deal_amount": 0,
"type": "buy", // "buy"、"sell"
"avg_price": 0, // If the exchange does not provide it, it can be assigned a value of 0 during processing.
}
}
trường phương thức:
{"limit":0,"since":0,"symbol":"ETH_USDT"}
Dữ liệu mà docker mong đợi trong phản hồi giao thức tùy chỉnh:
{
"data": [
{
"id": "ETH-USDT,123456",
"symbol": "ETH_USDT",
"amount": 0.25,
"price": 1005,
"deal_amount": 0,
"avg_price": 1000,
"type": "buy", // "buy"、"sell"
"status": "filled", // "filled"
},
// ...
]
}
trường phương thức:
{"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
Dữ liệu mà docker mong đợi trong phản hồi giao thức tùy chỉnh:
{
"data": true // As long as there is no error field in the JSON, the order cancellation is considered successful by default.
}
Cácexchange.IOchức năng được sử dụng để truy cập giao diện trao đổi trực tiếp.
GET /api/v5/trade/orders-pending, parameters: instType=SPOT, instId=ETH-USDT
ví dụ.
// Called in the strategy instance
exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")
trường phương pháp:"__api_/api/v5/trade/orders-pending"
, trường phương thức bắt đầu với _nấm, cho thấy rằng điều này được kích hoạt bởiexchange.IOgọi hàm trong phiên bản chiến lược.
trường tham số:
{"instId":"ETH-USDT","instType":"SPOT"} // instType=SPOT&instId=ETH-USDT encoded parameters will be restored to JSON
Dữ liệu mà docker mong đợi trong phản hồi giao thức tùy chỉnh:
{
"data": {"code": "0", "data": [], "msg": ""} // The data attribute value is the data of the exchange API: GET /api/v5/trade/orders-pending response
}
exchange.Go()
, exchange.GetRawJSON()
và các chức năng khác không cần phải được đóng gói, và phương pháp gọi và chức năng vẫn không thay đổi.Ngoài việc hỗ trợ tất cả các chức năng của sàn giao dịch tại chỗ, sàn giao dịch tương lai cũng có một số chức năng API độc đáo cho sàn giao dịch tương lai.
Phải thực hiện
REST Custom Protocol - Truy cập vào giao diện OKX Exchange REST API, được đóng gói dưới dạng đối tượng trao đổi tại chỗ. Thực hiện một yêu cầu giao diện công cộng và đáp ứng dữ liệu đóng gói. Thực hiện một chữ ký giao diện riêng, yêu cầu và phản hồi dữ liệu đóng gói. Ví dụ này chủ yếu là để thử nghiệm và học tập. Các giao diện khác sử dụng dữ liệu mô phỏng để phản hồi trực tiếp với docker để thử nghiệm.
import http.server
import socketserver
import json
import urllib.request
import urllib.error
import argparse
import ssl
import hmac
import hashlib
import base64
from datetime import datetime
ssl._create_default_https_context = ssl._create_unverified_context
class BaseProtocol:
ERR_NOT_SUPPORT = {"error": "not support"}
def __init__(self, apiBase, accessKey, secretKey):
self._apiBase = apiBase
self._accessKey = accessKey
self._secretKey = secretKey
def _httpRequest(self, method, path, query="", params={}, addHeaders={}):
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'
}
# add headers
for key in addHeaders:
headers[key] = addHeaders[key]
if method == "GET":
url = f"{self._apiBase}{path}?{query}" if query != "" else f"{self._apiBase}{path}"
req = urllib.request.Request(url, method=method, headers=headers)
else:
url = f"{self._apiBase}{path}"
req = urllib.request.Request(url, json.dumps(params, separators=(',', ':')).encode('utf-8'), method=method, headers=headers)
print(f'send request by protocol: {self.exName}, req:', req.method, req.full_url, req.headers, req.data, "\n")
try:
with urllib.request.urlopen(req) as resp:
data = json.loads(resp.read())
except json.JSONDecodeError:
data = {"error": "Invalid JSON response"}
except urllib.error.HTTPError as e:
data = {"error": f"HTTP error: {e.code}"}
except urllib.error.URLError as e:
data = {"error": f"URL error: {e.reason}"}
except Exception as e:
data = {"error": f"Exception occurred: {str(e)}"}
print(f'protocol response received: {self.exName}, resp:', data, "\n")
return data
def GetTickers(self):
return self.ERR_NOT_SUPPORT
def GetMarkets(self):
return self.ERR_NOT_SUPPORT
def GetTicker(self, symbol):
return self.ERR_NOT_SUPPORT
def GetDepth(self, symbol=""):
return self.ERR_NOT_SUPPORT
def GetTrades(self, symbol=""):
return self.ERR_NOT_SUPPORT
def GetRecords(self, symbol, period, limit):
return self.ERR_NOT_SUPPORT
def GetAssets(self):
return self.ERR_NOT_SUPPORT
def GetAccount(self):
return self.ERR_NOT_SUPPORT
def CreateOrder(self, symbol, side, price, amount):
return self.ERR_NOT_SUPPORT
def GetOrders(self, symbol=""):
return self.ERR_NOT_SUPPORT
def GetOrder(self, orderId):
return self.ERR_NOT_SUPPORT
def CancelOrder(self, orderId):
return self.ERR_NOT_SUPPORT
def GetHistoryOrders(self, symbol, since, limit):
return self.ERR_NOT_SUPPORT
def GetPostions(self, symbol=""):
return self.ERR_NOT_SUPPORT
def SetMarginLevel(self, symbol, marginLevel):
return self.ERR_NOT_SUPPORT
def GetFundings(self, symbol=""):
return self.ERR_NOT_SUPPORT
def IO(self, params):
return self.ERR_NOT_SUPPORT
class ProtocolFactory:
@staticmethod
def createExWrapper(apiBase, accessKey, secretKey, exName) -> BaseProtocol:
if exName == "OKX":
return CustomProtocolOKX(apiBase, accessKey, secretKey, exName)
else:
raise ValueError(f'Unknown exName: {exName}')
class CustomProtocolOKX(BaseProtocol):
"""
CustomProtocolOKX - OKX API Wrapper
# TODO: add information.
"""
def __init__(self, apiBase, accessKey, secretKey, exName):
secretKeyList = secretKey.split(",")
self.exName = exName
self._x_simulated_trading = 0
if len(secretKeyList) > 1:
self._passphrase = secretKeyList[1]
if len(secretKeyList) > 2:
if secretKeyList[2] == "simulate":
self._x_simulated_trading = 1
else:
raise ValueError(f"{self.exName}: invalid secretKey format.")
super().__init__(apiBase, accessKey, secretKeyList[0])
def getCurrencys(self, symbol):
baseCurrency, quoteCurrency = "", ""
arrCurrency = symbol.split("_")
if len(arrCurrency) == 2:
baseCurrency = arrCurrency[0]
quoteCurrency = arrCurrency[1]
return baseCurrency, quoteCurrency
def getSymbol(self, instrument):
arrCurrency = instrument.split("-")
if len(arrCurrency) == 2:
baseCurrency = arrCurrency[0]
quoteCurrency = arrCurrency[1]
else:
raise ValueError(f"{self.exName}: invalid instrument: {instrument}")
return f'{baseCurrency}_{quoteCurrency}'
def callUnsignedAPI(self, httpMethod, path, query="", params={}):
return self._httpRequest(httpMethod, path, query, params)
def callSignedAPI(self, httpMethod, path, query="", params={}):
strTime = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
jsonStr = json.dumps(params, separators=(',', ':')) if len(params) > 0 else ""
message = f'{strTime}{httpMethod}{path}{jsonStr}'
if httpMethod == "GET" and query != "":
message = f'{strTime}{httpMethod}{path}?{query}{jsonStr}'
mac = hmac.new(bytes(self._secretKey, encoding='utf8'), bytes(message, encoding='utf-8'), digestmod='sha256')
signature = base64.b64encode(mac.digest())
headers = {}
if self._x_simulated_trading == 1:
headers["x-simulated-trading"] = str(self._x_simulated_trading)
headers["OK-ACCESS-KEY"] = self._accessKey
headers["OK-ACCESS-PASSPHRASE"] = self._passphrase
headers["OK-ACCESS-TIMESTAMP"] = strTime
headers["OK-ACCESS-SIGN"] = signature
return self._httpRequest(httpMethod, path, query, params, headers)
# Encapsulates requests to the exchange API.
def GetTicker(self, symbol):
"""
GET /api/v5/market/ticker , param: instId
"""
baseCurrency, quoteCurrency = self.getCurrencys(symbol)
if baseCurrency == "" or quoteCurrency == "":
return {"error": "invalid symbol"}
path = "/api/v5/market/ticker"
query = f'instId={baseCurrency}-{quoteCurrency}'
data = self.callUnsignedAPI("GET", path, query=query)
if "error" in data.keys() and "data" not in data.keys():
return data
ret_data = {}
if data["code"] != "0" or not isinstance(data["data"], list):
return {"error": json.dumps(data, ensure_ascii=False)}
for tick in data["data"]:
if not all(k in tick for k in ("instId", "bidPx", "askPx", "high24h", "low24h", "vol24h", "ts")):
return {"error": json.dumps(data, ensure_ascii=False)}
ret_data["symbol"] = self.getSymbol(tick["instId"])
ret_data["buy"] = tick["bidPx"]
ret_data["sell"] = tick["askPx"]
ret_data["high"] = tick["high24h"]
ret_data["low"] = tick["low24h"]
ret_data["open"] = tick["open24h"]
ret_data["last"] = tick["last"]
ret_data["vol"] = tick["vol24h"]
ret_data["time"] = tick["ts"]
return {"data": ret_data, "raw": data}
def GetDepth(self, symbol):
"""
TODO: Implementation code
"""
# Mock data for testing.
ret_data = {
"time" : 1500793319499,
"asks" : [
[1000, 0.5], [1001, 0.23], [1004, 2.1]
],
"bids" : [
[999, 0.25], [998, 0.8], [995, 1.4]
]
}
return {"data": ret_data}
def GetTrades(self, symbol):
"""
TODO: Implementation code
"""
# Mock data for testing.
ret_data = [
{
"id": 12232153,
"time" : 1529919412968,
"price": 1000,
"amount": 0.5,
"type": "buy",
}, {
"id": 12545664,
"time" : 1529919412900,
"price": 1001,
"amount": 1,
"type": "sell",
}
]
return {"data": ret_data}
def GetRecords(self, symbol, period, limit):
"""
TODO: Implementation code
"""
# Mock data for testing.
ret_data = [
[1500793319, 1.1, 2.2, 3.3, 4.4, 5.5],
[1500793259, 1.01, 2.02, 3.03, 4.04, 5.05],
]
return {"data": ret_data}
def GetMarkets(self):
"""
TODO: Implementation code
"""
ret_data = {}
return {"data": ret_data}
def GetTickers(self):
"""
TODO: Implementation code
"""
ret_data = {}
return {"data": ret_data}
def GetAccount(self):
"""
GET /api/v5/account/balance
"""
path = "/api/v5/account/balance"
data = self.callSignedAPI("GET", path)
ret_data = []
if data["code"] != "0" or "data" not in data or not isinstance(data["data"], list):
return {"error": json.dumps(data, ensure_ascii=False)}
for ele in data["data"]:
if "details" not in ele or not isinstance(ele["details"], list):
return {"error": json.dumps(data, ensure_ascii=False)}
for detail in ele["details"]:
asset = {"currency": detail["ccy"], "free": detail["availEq"], "frozen": detail["ordFrozen"]}
if detail["availEq"] == "":
asset["free"] = detail["availBal"]
ret_data.append(asset)
return {"data": ret_data, "raw": data}
def GetAssets(self):
"""
TODO: Implementation code
"""
# Mock data for testing.
ret_data = [
{"currency": "TUSD", "free": "3000", "frozen": "0"},
{"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}
]
return {"data": ret_data}
def CreateOrder(self, symbol, side, price, amount):
"""
TODO: Implementation code
"""
# Mock data for testing.
ret_data = {
"id": "BTC-USDT,123456"
}
return {"data": ret_data}
def GetOrders(self, symbol):
"""
GET /api/v5/trade/orders-pending instType SPOT instId after limit
"""
baseCurrency, quoteCurrency = self.getCurrencys(symbol)
if baseCurrency == "" or quoteCurrency == "":
return {"error": "invalid symbol"}
path = "/api/v5/trade/orders-pending"
after = ""
limit = 100
ret_data = []
while True:
query = f"instType=SPOT&instId={baseCurrency}-{quoteCurrency}&limit={limit}"
if after != "":
query = f"instType=SPOT&instId={baseCurrency}-{quoteCurrency}&limit={limit}&after={after}"
data = self.callSignedAPI("GET", path, query=query)
if data["code"] != "0" or not isinstance(data["data"], list):
return {"error": json.dumps(data, ensure_ascii=False)}
for ele in data["data"]:
order = {}
order["id"] = f'{ele["instId"]},{ele["ordId"]}'
order["symbol"] = f'{baseCurrency}-{quoteCurrency}'
order["amount"] = ele["sz"]
order["price"] = ele["px"]
order["deal_amount"] = ele["accFillSz"]
order["avg_price"] = 0 if ele["avgPx"] == "" else ele["avgPx"]
order["type"] = "buy" if ele["side"] == "buy" else "sell"
order["state"] = "pending"
ret_data.append(order)
after = ele["ordId"]
if len(data["data"]) < limit:
break
return {"data": ret_data}
def GetOrder(self, orderId):
"""
TODO: Implementation code
"""
# Mock data for testing.
ret_data = {
"id": "ETH-USDT,123456",
"symbol": "ETH_USDT",
"amount": 0.15,
"price": 1002,
"status": "pending",
"deal_amount": 0,
"type": "buy",
"avg_price": 0,
}
return {"data": ret_data}
def GetHistoryOrders(self, symbol, since, limit):
"""
TODO: Implementation code
"""
# Mock data for testing.
ret_data = [
{
"id": "ETH-USDT,123456",
"symbol": "ETH_USDT",
"amount": 0.25,
"price": 1005,
"deal_amount": 0,
"avg_price": 1000,
"type": "buy",
"status": "filled"
}
]
return {"data": ret_data}
def CancelOrder(self, orderId):
"""
TODO: Implementation code
"""
# Mock data for testing.
ret_data = True
return {"data": ret_data}
def IO(self, httpMethod, path, params={}):
if httpMethod == "GET":
query = urllib.parse.urlencode(params)
data = self.callSignedAPI(httpMethod, path, query=query)
else:
data = self.callSignedAPI(httpMethod, path, params=params)
if data["code"] != "0":
return {"error": json.dumps(data, ensure_ascii=False)}
return {"data": data}
class HttpServer(http.server.SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
self.request_body = None
self.request_path = None
super().__init__(*args, **kwargs)
def log_message(self, format, *args):
return
def _sendResponse(self, body):
self.send_response(200)
self.send_header('Content-type', 'application/json; charset=utf-8')
self.end_headers()
self.wfile.write(json.dumps(body).encode('utf-8'))
def do_GET(self):
# The FMZ.COM custom protocol only send GET method request
self._sendResponse({"error": "not support GET method."})
def do_POST(self):
"""
Returns:
json: success, {"data": ...}
json: error, {"error": ...}
"""
contentLen = int(self.headers['Content-Length'])
self.request_body = self.rfile.read(contentLen)
self.request_path = self.path
exName = self.request_path.lstrip("/")
# Print the request received from the FMZ.COM robot
print(f"--------- request received from the FMZ.COM robot: --------- \n {self.requestline} | Body: {self.request_body} | Headers: {self.headers} \n")
try:
data = json.loads(self.request_body)
except json.JSONDecodeError:
data = {"error": self.request_body.decode('utf-8')}
self._sendResponse(data)
return
# fault tolerant
if not all(k in data for k in ("access_key", "secret_key", "method", "params")):
data = {"error": "missing required parameters"}
self._sendResponse(data)
return
respData = {}
accessKey = data["access_key"]
secretKey = data["secret_key"]
method = data["method"]
params = data["params"]
exchange = ProtocolFactory.createExWrapper("https://www.okx.com", accessKey, secretKey, exName)
if method == "ticker":
symbol = str(params["symbol"]).upper()
respData = exchange.GetTicker(symbol)
elif method == "depth":
symbol = str(params["symbol"]).upper()
respData = exchange.GetDepth(symbol)
elif method == "trades":
symbol = str(params["symbol"]).upper()
respData = exchange.GetTrades(symbol)
elif method == "records":
symbol = str(params["symbol"]).upper()
period = int(params["period"])
limit = int(params["limit"])
respData = exchange.GetRecords(symbol, period, limit)
elif method == "accounts":
respData = exchange.GetAccount()
elif method == "assets":
respData = exchange.GetAssets()
elif method == "trade":
amount = float(params["amount"])
price = float(params["price"])
symbol = str(params["symbol"])
tradeType = str(params["type"])
respData = exchange.CreateOrder(symbol, tradeType, price, amount)
elif method == "orders":
symbol = str(params["symbol"]).upper()
respData = exchange.GetOrders(symbol)
elif method == "order":
orderId = str(params["id"])
respData = exchange.GetOrder(orderId)
elif method == "historyorders":
symbol = str(params["symbol"])
since = int(params["since"])
limit = int(params["limit"])
respData = exchange.GetHistoryOrders(symbol, since, limit)
elif method == "cancel":
orderId = str(params["id"])
respData = exchange.CancelOrder(orderId)
elif method[:6] == "__api_":
respData = exchange.IO(self.headers["Http-Method"], method[6:], params)
else:
respData = {"error": f'invalid method: {method}'}
# Print the response to send to FMZ.COM robot
print(f"response to send to FMZ.COM robot: {respData} \n")
self._sendResponse(respData)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Run a FMZ.COM custom protocol plugin.")
parser.add_argument("--port", type=int, default=6666, help="Port to run the server on.")
parser.add_argument("--address", type=str, default="localhost", help="Address to bind the server to.")
args = parser.parse_args()
with socketserver.TCPServer((args.address, args.port), HttpServer) as httpd:
print(f"running... {args.address}:{args.port}", "\n")
httpd.serve_forever()