O recurso está a ser carregado... Carregamento...

FMZ Quant Trading Platform Guia de acesso ao protocolo personalizado

Autora:FMZ~Lydia, Criado: 2024-11-08 16:37:25, Atualizado: 2024-11-15 10:23:15

img

A plataforma de negociação FMZ Quant suporta muitas trocas de criptomoedas e encapsula as principais trocas no mercado. No entanto, ainda há muitas trocas que não são encapsuladas. Para os usuários que precisam usar essas trocas, eles podem acessá-las através do protocolo personalizado FMZ Quant.RESTprotocolo ouFIXO protocolo também pode ser acedido.

Este artigo abordaráRESTacesso protocolo como um exemplo para explicar como usar o protocolo personalizado da FMZ Quant Trading Platform para encapsular e acessar a API da OKX exchange.

  • O fluxo de trabalho do protocolo personalizado é: Processo de solicitação: Instança de estratégia executada no docker -> Programa de protocolo personalizado -> Exchange API Processo de resposta: Exchange API -> Programa de protocolo personalizado -> Instança de estratégia em execução no docker

1. Configurar o Exchange

A página para a configuração da troca na plataforma de negociação quantitativa FMZ:

https://www.fmz.com/m/platforms/add

img

  • Selecionar protocolo: Selecionar Protocolo personalizado.
  • Endereço do serviço: O programa de protocolo personalizado é essencialmente um serviço RPC. Portanto, é necessário especificar o endereço de serviço e a porta claramente ao configurar o intercâmbio.Strategy instance running on the docker -> Custom protocol program- Não. Por exemplo:http://127.0.0.1:6666/OKX, o programa de protocolo personalizado e o docker geralmente são executados no mesmo dispositivo (servidor), de modo que o endereço do serviço é escrito como a máquina local (localhost), e a porta pode ser usada como uma porta que não é ocupada pelo sistema.
  • Chave de acesso: A informação de configuração de troca transmitida durante o processoStrategy instance running on the docker -> Custom protocol program.
  • Chave secreta: A informação de configuração de troca transmitida durante o processoStrategy instance running on the docker -> Custom protocol program.
  • Tag: A etiqueta do objeto de troca na plataforma de negociação quantitativa FMZ, utilizada para identificar um determinado objeto de troca.

A captura de tela da configuração do plug-in OKX revelada no artigo é a seguinte:

img

Informações de configuração de chave secreta de troca OKX:

accessKey:  accesskey123    // accesskey123, these are not actual keys, just for demonstration
secretKey:  secretkey123
passphrase: passphrase123

2. Implementação do Docker e do Custom Protocol Program (plugin)

    1. Docker Um docker deve ser implantado para executar qualquer estratégia na plataforma de negociação FMZ Quant. Para detalhes sobre como implementar um docker, consulte o tutorial da plataforma.
    1. Programa de protocolo personalizado (plugin) Docker e o protocolo personalizado são geralmente implantados no mesmo dispositivo. O programa de protocolo (serviço) personalizado pode ser escrito em qualquer linguagem. Este artigo é escrito em Python3. Assim como executar qualquer programa Python, você pode executá-lo diretamente (fazer várias configurações do ambiente Python com antecedência). Claro, FMZ também suporta a execução de programas Python, e você também pode executar este protocolo personalizado como uma negociação ao vivo para fornecer a FMZ Quant Trading Platform com suporte para acesso de API de troca desempacotado. Após o programa de protocolo personalizado ser executado, ele começa a ouvir:http://127.0.0.1:6666O programa de protocolo personalizado pode processar caminhos específicos, tais como/OKX.

3. Instança de estratégia solicita FMZ API Função

Quando a função API da plataforma (FMZ) é chamada na estratégia, o programa de protocolo personalizado receberá um pedido do docker.

Página de ferramentas de depuração:

https://www.fmz.com/m/debug

function main() {
    return exchange.GetTicker("LTC_USDT")
}

Chamando a funçãoexchange.GetTicker(), o programa de protocolo personalizado recebe o pedido:

POST /OKX HTTP/1.1 
{
    "access_key":"xxx",
    "method":"ticker",
    "nonce":1730275031047002000,
    "params":{"symbol":"LTC_USDT"},
    "secret_key":"xxx"
}
  • access_key: A chave de troca configurada na plataforma Configure Exchange acima
  • secret_key: A chave de troca configurada na plataforma Configure Exchange acima
  • Método: relacionado com a interface de chamada na estratégia, quando chamadaexchange.GetTicker(), methodéticker.
  • Nonce: A data e hora em que ocorreu a solicitação.
  • Parâmetros: Parâmetros relacionados com a chamada da interface na estratégia, neste exemplo, quando chamadaexchange.GetTicker(), os parâmetros correspondentes são:{"symbol":"LTC_USDT"}.

4. Acesso de programa de protocolo personalizado à interface de troca

Quando o programa de protocolo personalizado recebe uma solicitação do docker, ele pode obter informações como a função API da plataforma (incluindo informações de parâmetros) solicitada pela estratégia, a chave de troca, etc. com base nas informações transportadas na solicitação.

Com base nessas informações, o programa de protocolo personalizado pode acessar a interface de troca para obter os dados necessários ou executar certas operações.

Normalmente, a interface de troca tem métodos como GET/POST/PUT/DELETE, que são divididos em interface pública e interface privada.

  • Interface pública: uma interface que não requer verificação de assinatura e é solicitada diretamente em um programa de protocolo personalizado.
  • Interface privada: uma interface que requer verificação de assinatura. A assinatura precisa ser implementada no programa de protocolo personalizado para solicitar a interface API dessas trocas.

O programa de protocolo personalizado recebe os dados de resposta da interface de troca e os processa para construir os dados esperados pelo docker (descritos abaixo).GetTicker, GetAccounte outras funções na implementação da classe CustomProtocolOKX no exemplo de protocolo personalizado Python.

5. O programa de protocolo personalizado responde aos dados do Docker

Quando o programa de protocolo personalizado acessa a interface API do exchange, executa certas operações ou obtém certos dados, ele precisa fornecer os resultados ao docker.

Os dados enviados ao docker variam de acordo com a interface chamada pela estratégia e são divididos em duas categorias:

  • O programa de protocolo personalizado chama a interface de troca com sucesso:
{
    "data": null,  // "data" can be of any type 
    "raw": null    // "raw" can be of any type 
}

dados: a estrutura específica deste campo está relacionada com amethodna solicitação recebida pelo programa de protocolo personalizado, e é usado para construir a estrutura de dados finalmente devolvida pela função API da plataforma FMZ. Todas as interfaces serão listadas abaixo. raw: Este campo pode ser usado para passar nos dados brutos da resposta da interface da API de troca, como a estrutura do Ticker devolvida peloexchange.GetTicker()O campo "Info" da estrutura "Ticker" regista os dados dorawcampo e odataA partir daí, a plataforma pode utilizar os dados de base de dados de um campo de dados; algumas das funções API da plataforma não necessitam destes dados.

  • O programa de protocolo personalizado não conseguiu chamar a interface de troca (erro de negócios, erro de rede, etc.)
{
    "error": ""    // "error" contains an error message as a string
}

erro: informação sobre o erro, que será exibida no registo de erros na área de registo da plataforma de negociação ao vivo (FMZ), ferramenta de depuração e outras páginas.

Demonstra os dados de resposta de protocolo personalizados recebidos pelo programa de estratégia:

// 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"))
}

img

6. Acordo de estrutura de dados no protocolo personalizado

O processo acima é um breve processo do programa de protocolo personalizado que participa no acesso à API de troca (FMZ desempacotada).exchange.GetTicker()A seguir serão explicados os detalhes de interação de todas as funções da API da plataforma em detalhes.

A plataforma encapsula as funções comuns de várias exchanges e unifica-as em uma determinada função, como a função GetTicker, que solicita as informações atuais do mercado de um determinado produto. Esta é uma API que basicamente todas as exchanges têm. Portanto, ao acessar a interface API encapsulada pela plataforma em uma instância de estratégia, o docker enviará uma solicitação ao programa plug-in Custom Protocol (mencionado acima):

POST /OKX HTTP/1.1 
{
    "access_key": "xxx",
    "method": "ticker",
    "nonce": 1730275031047002000,
    "params": {"symbol":"LTC_USDT"},
    "secret_key": "xxx"
}

Ao chamar diferentes funções API encapsuladas da plataforma FMZ na estratégia (como GetTicker), o formato de solicitação enviado pelo docker para o protocolo personalizado também será diferente.methodeparams. Ao projetar um protocolo personalizado, execute operações específicas de acordo com o conteúdo do método.

Câmbio spot

Por exemplo, o par de negociação atual é:ETH_USDTOs dados que o docker espera que o protocolo personalizado responda são principalmente escritos no campo de dados, e um campo bruto também pode ser adicionado para registrar os dados originais da interface de troca.

  • GetTicker

Campo de método: ticker Campo de parâmetros:

{"symbol":"ETH_USDT"}

Dados que o docker espera na resposta do protocolo personalizado:

{
    "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

Campo do método: profundidade Campo de parâmetros:

{"limit":"30","symbol":"ETH_USDT"}

Dados que o docker espera na resposta do protocolo personalizado:

{
    "data" : {
        "time" : 1500793319499,
        "asks" : [
            [1000, 0.5], [1001, 0.23], [1004, 2.1]
            // ... 
        ],
        "bids" : [
            [999, 0.25], [998, 0.8], [995, 1.4]
            // ... 
        ]
    }
}
  • GetTrades

Campo do método: trades Campo de parâmetros:

{"symbol":"eth_usdt"}

Dados que o docker espera na resposta do protocolo personalizado:

{ 
    "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

Campo de método: registros Campo de parâmetros:

{
    "limit":"500",
    "period":"60",          // 60 minutes
    "symbol":"ETH_USDT"
}

Dados que o docker espera na resposta do protocolo personalizado:

{
    "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],
            // ...
    ]
}
  • GetMarketsA aplicar

Campo de método: Campo de parâmetros:

{}

Dados que o docker espera na resposta do protocolo personalizado:

{}
  • GetTickersA aplicar

Campo de método: Campo de parâmetros:

{}

Dados que o docker espera na resposta do protocolo personalizado:

{}
  • GetAccount

Campo de método: contas Campo de parâmetros:

{}

Dados que o docker espera na resposta do protocolo personalizado:

{
    "data": [
        {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
        {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
        // ...
    ]
}
  • GetAssets

Campo do método: ativos Campo de parâmetros:

{}

Dados que o docker espera na resposta do protocolo personalizado:

{
    "data": [
        {"currency": "TUSD", "free": "3000", "frozen": "0"},
        {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
        // ...
    ]
}
  • Criar Ordem / Comprar / Vender

Campo do método: comércio Campo de parâmetros:

{"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}

Dados que o docker espera na resposta do protocolo personalizado:

{
    "data": {
        "id": "BTC-USDT,123456"
    }
}
  • GetOrders

Campo de método: ordens Campo de parâmetros:

{"symbol":"ETH_USDT"}

Dados que o docker espera na resposta do protocolo personalizado:

{
    "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

Campo de método: ordem Campo de parâmetros:

{
    "id":"ETH-USDT,123456",       // Calling in the strategy: exchange.GetOrder("ETH-USDT,123456")
    "symbol":"ETH_USDT"
}

Dados que o docker espera na resposta do protocolo personalizado:

{ 
    "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.
    }
}
  • Ordens do GetHistory

campo de método: historyorders Campo de parâmetros:

{"limit":0,"since":0,"symbol":"ETH_USDT"}

Dados que o docker espera na resposta do protocolo personalizado:

{
    "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"
        }, 
        // ...
    ]
}
  • Cancelar encomenda

Campo de método: cancelar Campo de parâmetros:

{"id":"ETH-USDT,123456","symbol":"ETH_USDT"}

Dados que o docker espera na resposta do protocolo personalizado:

{
    "data": true    // As long as there is no error field in the JSON, the order cancellation is considered successful by default.
}
  • O

Oexchange.IOA função é usada para acessar a interface de troca diretamente.GET /api/v5/trade/orders-pending, parameters: instType=SPOT, instId=ETH-USDTcomo exemplo.

// Called in the strategy instance
exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")

campo método:"__api_/api/v5/trade/orders-pending", o campo método começa com _api, indicando que esta é desencadeada peloexchange.IOchamada de função na instância de estratégia. Campo de parâmetros:

{"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT encoded parameters will be restored to JSON

Dados que o docker espera na resposta do protocolo personalizado:

{
    "data": {"code": "0", "data": [], "msg": ""}    // The data attribute value is the data of the exchange API: GET /api/v5/trade/orders-pending response
}
  • Outros Outras funções da API da plataforma FMZ utilizadas nos exemplos de estratégia, tais como:exchange.Go(), exchange.GetRawJSON()e outras funções não precisam ser encapsuladas, e o método de chamada e a função permanecem inalterados.

Troca de futuros

Além de suportar todas as funções das bolsas spot, as bolsas de futuros também possuem algumas funções de API que são exclusivas das bolsas de futuros.

A aplicar

  • GetPositions
  • SetMarginLevel
  • GetFundings

Exemplo de protocolo personalizado na versão Python

Protocolo personalizado REST - Acesso à interface REST API da troca OKX, encapsulada como um objeto de troca spot. Implementou uma requisição pública de interface e encapsulamento de dados de resposta. Implementou uma assinatura de interface privada, encapsulamento de dados de solicitação e resposta. Este exemplo é principalmente para testes e aprendizado. As outras interfaces usam dados simulados para responder diretamente ao docker para testes.

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()

Mais.