[TOC]
Nền tảng giao dịch định lượng Inventor hỗ trợ nhiều sàn giao dịch tiền điện tử và bao gồm các sàn giao dịch chính thống trên thị trường. Tuy nhiên, vẫn còn nhiều sàn giao dịch chưa đượ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 thông qua giao thức chung được định lượng bởi nhà phát minh. Không giới hạn ở các sàn giao dịch tiền điện tử, bất kỳRESTThỏa thuận hoặcFIXNền tảng của thỏa thuận cũng có thể được truy cập.
Bài viết này sẽRESTLấy quyền truy cập giao thức làm ví dụ, bài viết giải thích cách sử dụng giao thức chung của Nền tảng giao dịch định lượng Inventor để đó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 chung REST.
Trang cấu hình trao đổi trên Nền tảng giao dịch định lượng Inventor:
托管者上运行的策略实例 -> 通用协议程序
Trong quá trình này, máy chủ biết nơi để truy cập vào chương trình giao thức chung.
Ví dụ:http://127.0.0.1:6666/OKX
Thông thường, chương trình giao thức chung và máy chủ được chạy trên cùng một thiết bị (máy chủ), do đó địa chỉ dịch vụ được ghi là máy cục bộ (localhost) và cổng có thể là cổng không bị hệ thống chiếm dụng.托管者上运行的策略实例 -> 通用协议程序
Thông tin cấu hình trao đổi được truyền trong quá trình này.托管者上运行的策略实例 -> 通用协议程序
Thông tin cấu hình trao đổi được truyền trong quá trình này.Ảnh chụp màn hình cấu hình plug-in OKX được tiết lộ trong bài viết như sau:
Thông tin cấu hình khóa bí mật trao đổi OKX:
accessKey: accesskey123 // accesskey123 这些并不是实际秘钥,仅仅是演示
secretKey: secretkey123
passphrase: passphrase123
http://127.0.0.1:6666
Trong chương trình giao thức chung, các đường dẫn cụ thể có thể được chỉ định, ví dụ/OKX
để được xử lý.Khi hàm API nền tảng (FMZ) được gọi trong chiến lược, Chương trình Giao thức Chung sẽ nhận được yêu cầu từ người giám hộ. Bạn cũng có thể kiểm tra bằ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ọiexchange.GetTicker()
Chức năng, chương trình giao thức chung 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()
giờ,method
Đó làticker
。exchange.GetTicker()
Khi gọi, các tham số có liên quan là:{"symbol":"LTC_USDT"}
。Khi chương trình giao thức chung nhận được yêu cầu từ người giám hộ, 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ố) mà chiến lược yêu cầu, khóa trao đổi, v.v. dựa trên thông tin có trong yêu cầu.
Dựa trên thông tin này, chương trình giao thức chung có thể truy cập vào 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 khai và giao diện riêng tư.
Chương trình giao thức chung nhận dữ liệu phản hồi từ giao diện trao đổi, xử lý thêm và xây dựng thành dữ liệu mà người giám hộ mong đợi (được mô tả bên dưới).
Tham khảo OKX spot exchange, triển khai lớp CustomProtocolOKX trong ví dụ giao thức chung của PythonGetTicker
、GetAccount
Và các chức năng khác.
Khi chương trình giao thức chung truy cập vào giao diện API của sàn giao dịch, thực hiện một số hoạt động nhất định hoặc lấy dữ liệu nhất định, nó cần phản hồi kết quả cho bên giám sát.
Dữ liệu được phản hồi cho người giám hộ thay đổi tùy theo giao diện mà chiến lược gọi và trước 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 giống với cấu trúc trong yêu cầu mà chương trình giao thức chung nhận được.method
Sau đây là danh sách tất cả các giao diện được sử dụng để xây dựng cấu trúc dữ liệu được trả về bởi hàm API nền tảng FMZ.
Raw: Trường này có thể được sử dụng để truyền dữ liệu thô của phản hồi API trao đổi, ví dụexchange.GetTicker()
Cấu trúc Ticker được trả về bởi hàm có thông tin sau được ghi lại trong trường Info của cấu trúc Ticker:raw
Các cánh đồng vàdata
Dữ liệu của trường; một số hàm API nền tảng không cần dữ liệu này.
Chương trình giao thức chung không gọi được giao diện trao đổi (lỗi nghiệp vụ, lỗi mạng, v.v.)
{
"error": "" // "error" contains an error message as a string
}
Thể hiện dữ liệu phản hồi giao thức chung được chương trình chính sách nhận được:
// FMZ平台的调试工具中测试
function main() {
Log(exchange.GetTicker("USDT")) // 交易对不完整,缺少BaseCurrency部分,需要通用协议插件程序返回报错信息: {"error": "..."}
Log(exchange.GetTicker("LTC_USDT"))
}
Trên đây là một quy trình tóm tắt về cách chương trình giao thức chung tham gia vào việc truy cập API trao đổi (FMZ chưa đóng gói). Quy trình này chỉ giải thích cách gọi công cụ gỡ lỗi nền tảng (FMZ).exchange.GetTicker()
Quá trình chức năng. Tiếp theo, thông tin chi tiết về tương tác của tất cả các chức năng API nền tảng sẽ được giải thích chi tiết.
Nền tảng này đóng gói các chức năng chung của nhiều sàn giao dịch khác nhau và hợp 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. Về cơ bản, đây là API mà tất cả các sàn giao dịch đều có. Vì vậy, khi giao diện API được đóng gói trên nền tảng được truy cập trong phiên bản chiến lược, người giám hộ sẽ gửi yêu cầu đến plug-in “Universal Protocol” (đã đề cập ở trên):
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 của nhiều nền tảng phát minh khác nhau trong chiến lược (chẳng hạn như GetTicker), định dạng yêu cầu do người giám hộ gửi đến giao thức chung cũng sẽ khác nhau. Dữ liệu (JSON) trong phần thân chỉ khác nhau ởmethod
Vàparams
. Khi thiết kế một giao thức chung,method
Bạn có thể thực hiện các thao tác cụ thể dựa trên nội dung. Sau đây là các tình huống yêu cầu-phản hồi cho tất cả các giao diện.
Ví dụ, cặp giao dịch hiện tại là:ETH_USDT
Tôi sẽ không đi vào chi tiết sau. Dữ liệu mà người giám hộ mong đợi giao thức chung phản hồi chủ yếu được ghi vào trường dữ liệu và cũng có thể thêm một trường thô để ghi lại dữ liệu gốc của giao diện trao đổi.
GetTicker
{"symbol":"ETH_USDT"}
{
"data": {
"symbol": "ETH_USDT", // 对应GetTicker函数返回的Ticker结构中的Symbol字段
"buy": "2922.18", // ...对应Buy字段
"sell": "2922.19",
"high": "2955",
"low": "2775.15",
"open": "2787.72",
"last": "2922.18",
"vol": "249400.888156",
"time": "1731028903911"
},
"raw": {} // 可以增加一个raw字段记录交易所API接口应答的原始数据
}
GetDepth
{"limit":"30","symbol":"ETH_USDT"}
{
"data" : {
"time" : 1500793319499,
"asks" : [
[1000, 0.5], [1001, 0.23], [1004, 2.1]
// ...
],
"bids" : [
[999, 0.25], [998, 0.8], [995, 1.4]
// ...
]
}
}
GetTrades
{"symbol":"eth_usdt"}
{
"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",
}
// ...
]
}
GetRecords
{
"limit":"500",
"period":"60", // 60分钟
"symbol":"ETH_USDT"
}
{
"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],
// ...
]
}
GetMarkets Để được thực hiện
{}
{}
GetTickers Để được thực hiện
{}
{}
GetAccount
{}
{
"data": [
{"currency": "TUSD", "free": "3000", "frozen": "0"},
{"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"},
// ...
]
}
GetAssets
{}
{
"data": [
{"currency": "TUSD", "free": "3000", "frozen": "0"},
{"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"},
// ...
]
}
CreateOrder / Buy / Sell
{"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
{
"data": {
"id": "BTC-USDT,123456"
}
}
GetOrders
{"symbol":"ETH_USDT"}
{
"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"
},
// ...
]
}
GetOrder
{
"id":"ETH-USDT,123456", // 策略中调用:exchange.GetOrder("ETH-USDT,123456")
"symbol":"ETH_USDT"
}
{
"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, // 如果交易所没有提供,在处理时可以赋值为0
}
}
GetHistoryOrders
{"limit":0,"since":0,"symbol":"ETH_USDT"}
{
"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"
},
// ...
]
}
CancelOrder
{"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
{
"data": true // 只要该JSON中没有error字段,都默认为撤单成功
}
IO
Hàm exchange.IO được sử dụng để truy cập trực tiếp vào giao diện trao đổi. Ví dụ, chúng ta sử dụng
GET /api/v5/trade/orders-pending, 参数:instType=SPOT,instId=ETH-USDT
Ví dụ.
// 策略实例中调用
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 bằng _api, cho biết phương thức này được kích hoạt bởi lệnh gọi hàm exchange.IO trong phiên bản chiến lược.
trường params:
{"instId":"ETH-USDT","instType":"SPOT"} // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
Dữ liệu mà máy chủ mong đợi trong phản hồi giao thức chung:
{
"data": {"code": "0", "data": [], "msg": ""} // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
}
khác
Các hàm API khác của Inventor Platform được sử dụng trong các ví dụ về chiến lược, chẳng hạn như:
exchange.Go()
、exchange.GetRawJSON()
Các hàm như thế này không cần phải được đóng gói và phương thức gọi cũng như 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 giao ngay, sàn giao dịch tương lai còn có một số chức năng API dành riêng cho sàn giao dịch tương lai.
Để được thực hiện
Giao thức chung REST - truy cập vào giao diện REST API của sàn giao dịch OKX và đóng gói nó dưới dạng một đối tượng trao đổi tại chỗ. Đã triển khai giao diện chung cho việc đóng gói dữ liệu yêu cầu và phản hồi. Đã triển khai chữ ký giao diện riêng tư, đóng gói dữ liệu yêu cầu và phản hồi. Ví dụ này chủ yếu dùng để thử nghiệm và học hỏi. Các giao diện khác sử dụng dữ liệu mô phỏng để phản hồi trực tiếp tới máy chủ để 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'
if httpMethod == "GET":
jsonStr = json.dumps(params, separators=(',', ':')) if len(params) > 0 else ""
else:
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()