We FMZ Quant Trading Platform supports many cryptocurrency exchanges and encapsulates the mainstream exchanges on the market. However, there are still many exchanges that are not encapsulated. For users who need to use these exchanges, they can access them through the FMZ Quant Custom Protocol. Not only limited to cryptocurrency exchanges, any platform that supports the REST protocol or FIX protocol can also be accessed.
This article will take REST protocol access as an example to explain how to use the custom protocol of the FMZ Quant Trading Platform to encapsulate and access the API of the OKX exchange. Unless otherwise specified, this article refers to the REST custom protocol.
- The workflow of the custom protocol is:
Request process: Strategy instance running on the docker -> Custom protocol program -> Exchange API
Response process: Exchange API -> Custom protocol program -> Strategy instance running on the docker
1. Configure the Exchange
The page for configuring the exchange on the FMZ Quant Trading Platform:
- Select protocol: Select "Custom Protocol".
- Service address: The custom protocol program is essentially an RPC service.
Therefore, it is necessary to specify the service address and port clearly when configuring the exchange. In this way, the docker knows where to access the custom protocol program during the processStrategy instance running on the docker -> Custom protocol program
.
For example:http://127.0.0.1:6666/OKX
, the custom protocol program and the docker are usually run on the same device (server), so the service address is written as the local machine (localhost), and the port can be used as a port that is not occupied by the system. - Access Key:
The exchange configuration information passed during the processStrategy instance running on the docker -> Custom protocol program
. - Secret Key:
The exchange configuration information passed during the processStrategy instance running on the docker -> Custom protocol program
. - Tag:
The tag of the exchange object on the FMZ Quant Trading Platform, used to identify a certain exchange object.
The screenshot of the OKX plug-in configuration disclosed in the article is as follows:
OKX exchange secret key configuration information:
accessKey: accesskey123 // accesskey123, these are not actual keys, just for demonstrationsecretKey: secretkey123 passphrase: passphrase123
2. Deployment of the Docker and the Custom Protocol Program (plugin)
- Docker
A docker must be deployed to run any strategy on the FMZ Quant Trading Platform. For details on how to deploy a docker, please refer to the platform tutorial. It will not go into details here.
- Custom Protocol Program (plugin)
Docker and the custom protocol are usually deployed on the same device. The custom protocol (service) program can be written in any language. This article is written in Python3. Just like running any Python program, you can execute it directly (make various configurations of the Python environment in advance).
Of course, FMZ also supports running python programs, and you can also run this custom protocol as a live trading to provide the FMZ Quant Trading Platform with support for unpackaged exchange API access.
After the custom protocol program is running, it starts listening:http://127.0.0.1:6666
. The custom protocol program can process specific paths, such as/OKX
.
- Docker
3. Strategy Instance Requests FMZ API Function
When the (FMZ) platform API function is called in the strategy, the custom protocol program will receive a request from the docker. You can also test it using the platform's debugging tools, for example:
Debugging tools page:
functionmain() { return exchange.GetTicker("LTC_USDT") }
Calling the function exchange.GetTicker()
, the custom protocol program receives the request:
POST /OKXHTTP/1.1 { "access_key":"xxx", "method":"ticker", "nonce":1730275031047002000, "params":{"symbol":"LTC_USDT"}, "secret_key":"xxx" }
- access_key: The exchange key configured in the platform "Configure Exchange" above
- secret_key: The exchange key configured in the platform "Configure Exchange" above
- method: related to the calling interface in the strategy, when calling
exchange.GetTicker()
,method
isticker
. - nonce: The timestamp when the request occurred.
- params: Parameters related to the interface call in the strategy, in this example, when calling
exchange.GetTicker()
, the related parameters are:{"symbol":"LTC_USDT"}
.
4. Custom Protocol Program Access to Exchange Interface
When the custom protocol program receives a request from the docker, it can obtain information such as the platform API function (including parameter information) requested by the strategy, the exchange key, etc. based on the information carried in the request.
Based on this information, the custom protocol program can access the exchange interface to obtain the required data or perform certain operations.
Usually the exchange interface has methods such as GET/POST/PUT/DELETE, which are divided into public interface and private interface.
- Public interface: an interface that does not require signature verification and is directly requested in a custom protocol program.
- Private interface: an interface that requires signature verification. The signature needs to be implemented in the custom protocol program to request the API interface of these exchanges.
The custom protocol program receives the response data from the exchange interface and further processes it to construct the data expected by the docker (described below). Refer to OKX spot exchange, the GetTicker
, GetAccount
and other functions in the CustomProtocolOKX class implementation in the Python custom protocol example.
5. The Custom Protocol Program Responds the Data to the Docker
When the custom protocol program accesses the exchange's API interface, performs certain operations or obtains certain data, it needs to feed back the results to the docker.
The data fed back to the docker varies according to the interface called by the strategy, and is first divided into two categories:
- The custom protocol program calls the exchange interface successfully:
{ "data": null, // "data" can be of anytype"raw": null // "raw" can be of anytype }
data: The specific structure of this field is related to the method
in the request received by the custom protocol program, and is used to construct the data structure finally returned by the FMZ platform API function. All interfaces will be listed below.
raw: This field can be used to pass in the raw data of the exchange API interface response, such as the Ticker structure returned by the exchange.GetTicker()
function. The Info field of the Ticker structure records the data of the raw
field and the data
field; some of the platform's API functions do not need this data.
- The custom protocol program failed to call the exchange interface (business error, network error, etc.)
{ "error": "" // "error" contains an error message as a string }
error: error information, which will be displayed in the error log in the log area of the (FMZ) platform live trading, debugging tool and other pages.
Demonstrates the custom protocol response data received by the strategy program:
// Tested in the debugging tool of the FMZ platformfunctionmain() { 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")) }
6. Data Structure Agreement in Custom Protocol
The above is a brief process of the custom protocol program participating in the access to the (FMZ unpackaged) exchange API. This process only explains the process when calling the exchange.GetTicker()
function in the (FMZ) platform debugging tool. The following will explain the interaction details of all platform API functions in detail.
The platform encapsulates the common functions of various exchanges and unifies them into a certain function, such as the GetTicker function, which requests the current market information of a certain product. This is an API that basically all exchanges have. Therefore, when accessing the API interface encapsulated by the platform in a strategy instance, the docker will send a request to the "Custom Protocol" plug-in program (mentioned above):
POST /OKXHTTP/1.1 { "access_key": "xxx", "method": "ticker", "nonce": 1730275031047002000, "params": {"symbol":"LTC_USDT"}, "secret_key": "xxx" }
When calling different FMZ platform encapsulated API functions in the strategy (such as GetTicker), the request format sent by the docker to the custom protocol will also be different. The data (JSON) in the body only differs in method
and params
. When designing a custom protocol, perform specific operations according to the content of the method. The following are the request-response scenarios for all interfaces.
Spot Exchange
For example, the current trading pair is: ETH_USDT
, which will not be described in detail later. The data that the docker expects the custom protocol to respond to is mainly written in the data field, and a raw field can also be added to record the original data of the exchange interface.
- GetTicker
method field: "ticker"
params field:
{"symbol":"ETH_USDT"}
Data that the docker expects in the custom protocol response:
{ "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 }
- GetDepth
method field: "depth"
params field:
{"limit":"30","symbol":"ETH_USDT"}
Data that the docker expects in the custom protocol response:
{ "data" : { "time" : 1500793319499, "asks" : [ [1000, 0.5], [1001, 0.23], [1004, 2.1] // ... ], "bids" : [ [999, 0.25], [998, 0.8], [995, 1.4] // ... ] } }
- GetTrades
method field: "trades"
params field:
{"symbol":"eth_usdt"}
Data that the docker expects in the custom protocol response:
{ "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
method field: "records"
params field:
{ "limit":"500", "period":"60", // 60 minutes"symbol":"ETH_USDT" }
Data that the docker expects in the custom protocol response:
{ "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
To be implemented
method field: ""
params field:
{}
Data that the docker expects in the custom protocol response:
{}
- GetTickers
To be implemented
method field: ""
params field:
{}
Data that the docker expects in the custom protocol response:
{}
- GetAccount
method field: "accounts"
params field:
{}
Data that the docker expects in the custom protocol response:
{ "data": [ {"currency": "TUSD", "free": "3000", "frozen": "0"}, {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, // ... ] }
- GetAssets
method field: "assets"
params field:
{}
Data that the docker expects in the custom protocol response:
{ "data": [ {"currency": "TUSD", "free": "3000", "frozen": "0"}, {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, // ... ] }
- CreateOrder / Buy / Sell
method field: "trade"
params field:
{"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
Data that the docker expects in the custom protocol response:
{ "data": { "id": "BTC-USDT,123456" } }
- GetOrders
method field: "orders"
params field:
{"symbol":"ETH_USDT"}
Data that the docker expects in the custom protocol response:
{ "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
method field: "order"
params field:
{ "id":"ETH-USDT,123456", // Calling in the strategy: exchange.GetOrder("ETH-USDT,123456")"symbol":"ETH_USDT" }
Data that the docker expects in the custom protocol response:
{ "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. } }
- GetHistoryOrders
method field: "historyorders"
params field:
{"limit":0,"since":0,"symbol":"ETH_USDT"}
Data that the docker expects in the custom protocol response:
{ "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
method field: "cancel"
params field:
{"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
Data that the docker expects in the custom protocol response:
{ "data": true// As long as there is no error field in the JSON, the order cancellation is considered successful by default. }
- IO
The exchange.IO function is used to access the exchange interface directly. For example, let's take
GET /api/v5/trade/orders-pending, parameters: instType=SPOT, instId=ETH-USDT
as an example.
// Called in the strategy instance exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")
method field: "__api_/api/v5/trade/orders-pending"
, the method field starts with _api, indicating that this is triggered by the exchange.IO function call in the strategy instance.
params field:
{"instId":"ETH-USDT","instType":"SPOT"} // instType=SPOT&instId=ETH-USDT encoded parameters will be restored to JSON
Data that the docker expects in the custom protocol response:
{ "data": {"code": "0", "data": [], "msg": ""} // The data attribute value is the data of the exchange API: GET /api/v5/trade/orders-pending response }
- Others
Other FMZ platform API functions used in the strategy examples, such as:exchange.Go()
,exchange.GetRawJSON()
and other functions do not need to be encapsulated, and the calling method and function remain unchanged.
Futures Exchange
In addition to supporting all the functions of spot exchanges, futures exchanges also have some API functions that are unique to futures exchanges.
To be implemented
- GetPositions
- SetMarginLevel
- GetFundings
Custom Protocol Example in Python Version
REST Custom Protocol - Access to the OKX exchange REST API interface, encapsulated as a spot exchange object.
Implemented a public interface request and response data encapsulation.
Implemented a private interface signature, request and response data encapsulation.
This example is mainly for testing and learning. The other interfaces use simulated data to directly respond to the docker for testing.
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 classBaseProtocol: 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 headersfor key in addHeaders: headers[key] = addHeaders[key] if method == "GET": url = f"{self._apiBase}{path}?{query}"if query != ""elsef"{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 defGetTickers(self): return self.ERR_NOT_SUPPORT defGetMarkets(self): return self.ERR_NOT_SUPPORT defGetTicker(self, symbol): return self.ERR_NOT_SUPPORT defGetDepth(self, symbol=""): return self.ERR_NOT_SUPPORT defGetTrades(self, symbol=""): return self.ERR_NOT_SUPPORT defGetRecords(self, symbol, period, limit): return self.ERR_NOT_SUPPORT defGetAssets(self): return self.ERR_NOT_SUPPORT defGetAccount(self): return self.ERR_NOT_SUPPORT defCreateOrder(self, symbol, side, price, amount): return self.ERR_NOT_SUPPORT defGetOrders(self, symbol=""): return self.ERR_NOT_SUPPORT defGetOrder(self, orderId): return self.ERR_NOT_SUPPORT defCancelOrder(self, orderId): return self.ERR_NOT_SUPPORT defGetHistoryOrders(self, symbol, since, limit): return self.ERR_NOT_SUPPORT defGetPostions(self, symbol=""): return self.ERR_NOT_SUPPORT defSetMarginLevel(self, symbol, marginLevel): return self.ERR_NOT_SUPPORT defGetFundings(self, symbol=""): return self.ERR_NOT_SUPPORT defIO(self, params): return self.ERR_NOT_SUPPORT classProtocolFactory: @staticmethoddefcreateExWrapper(apiBase, accessKey, secretKey, exName) -> BaseProtocol: if exName == "OKX": return CustomProtocolOKX(apiBase, accessKey, secretKey, exName) else: raise ValueError(f'Unknown exName: {exName}') classCustomProtocolOKX(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 = 0iflen(secretKeyList) > 1: self._passphrase = secretKeyList[1] iflen(secretKeyList) > 2: if secretKeyList[2] == "simulate": self._x_simulated_trading = 1else: raise ValueError(f"{self.exName}: invalid secretKey format.") super().__init__(apiBase, accessKey, secretKeyList[0]) defgetCurrencys(self, symbol): baseCurrency, quoteCurrency = "", "" arrCurrency = symbol.split("_") iflen(arrCurrency) == 2: baseCurrency = arrCurrency[0] quoteCurrency = arrCurrency[1] return baseCurrency, quoteCurrency defgetSymbol(self, instrument): arrCurrency = instrument.split("-") iflen(arrCurrency) == 2: baseCurrency = arrCurrency[0] quoteCurrency = arrCurrency[1] else: raise ValueError(f"{self.exName}: invalid instrument: {instrument}") returnf'{baseCurrency}_{quoteCurrency}'defcallUnsignedAPI(self, httpMethod, path, query="", params={}): return self._httpRequest(httpMethod, path, query, params) defcallSignedAPI(self, httpMethod, path, query="", params={}): strTime = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' jsonStr = json.dumps(params, separators=(',', ':')) iflen(params) > 0else"" 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.defGetTicker(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"notin data.keys(): return data ret_data = {} if data["code"] != "0"ornotisinstance(data["data"], list): return {"error": json.dumps(data, ensure_ascii=False)} for tick in data["data"]: ifnotall(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} defGetDepth(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} defGetTrades(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} defGetRecords(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} defGetMarkets(self): """ TODO: Implementation code """ ret_data = {} return {"data": ret_data} defGetTickers(self): """ TODO: Implementation code """ ret_data = {} return {"data": ret_data} defGetAccount(self): """ GET /api/v5/account/balance """ path = "/api/v5/account/balance" data = self.callSignedAPI("GET", path) ret_data = [] if data["code"] != "0"or"data"notin data ornotisinstance(data["data"], list): return {"error": json.dumps(data, ensure_ascii=False)} for ele in data["data"]: if"details"notin ele ornotisinstance(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} defGetAssets(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} defCreateOrder(self, symbol, side, price, amount): """ TODO: Implementation code """# Mock data for testing. ret_data = { "id": "BTC-USDT,123456" } return {"data": ret_data} defGetOrders(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 = [] whileTrue: 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"ornotisinstance(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"] = 0if ele["avgPx"] == ""else ele["avgPx"] order["type"] = "buy"if ele["side"] == "buy"else"sell" order["state"] = "pending" ret_data.append(order) after = ele["ordId"] iflen(data["data"]) < limit: breakreturn {"data": ret_data} defGetOrder(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} defGetHistoryOrders(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} defCancelOrder(self, orderId): """ TODO: Implementation code """# Mock data for testing. ret_data = Truereturn {"data": ret_data} defIO(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} classHttpServer(http.server.SimpleHTTPRequestHandler): def__init__(self, *args, **kwargs): self.request_body = None self.request_path = Nonesuper().__init__(*args, **kwargs) deflog_message(self, format, *args): returndef_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')) defdo_GET(self): # The FMZ.COM custom protocol only send GET method request self._sendResponse({"error": "not support GET method."}) defdo_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 robotprint(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 tolerantifnotall(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 robotprint(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()