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:

https://www.fmz.com/m/platforms/add
  • 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 process Strategy 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 process Strategy instance running on the docker -> Custom protocol program.
  • Secret Key:
    The exchange configuration information passed during the process Strategy 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)

    1. 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.
    1. 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.

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:

https://www.fmz.com/m/debug
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 is ticker.
  • 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 GetTickerGetAccount 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()
    From: https://www.fmz.com/bbs-topic/10527

Leave a Reply

Your email address will not be published. Required fields are marked *