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

Guia de acesso ao Protocolo Geral para Inventores de Plataformas de Negociação Quantificadas

Autora:Inventor quantificado - sonho pequeno, Criado: 2024-10-29 14:37:56, Atualizado: 2024-11-12 21:58:55

[TOC]

img

As plataformas de negociação quantitativa do inventor suportam inúmeras trocas de criptomoedas e são fechadas para as principais trocas no mercado. No entanto, ainda há muitas trocas que não estão fechadas e para os usuários que precisam usá-las, o acesso é possível através do protocolo geral de quantificação do inventor.RESTacordo ouFIXA plataforma do protocolo também pode ser acessada.

Este artigo será sobreRESTO protocolo de acesso, por exemplo, explica como usar o protocolo geral da plataforma de negociação quantitativa do inventor para envelopar e acessar a API da OKX.

  • O processo de trabalho do Protocolo Geral é: Processo de solicitação: Exemplos de políticas executadas no administrador -> Procedimentos de protocolo geral -> API da bolsa Processo de resposta: API da bolsa -> Protocolo Geral -> Exemplos de políticas executadas no administrador

1 Configuração de bolsas

A página do inventor da plataforma de negociação quantitativa para configurar a bolsa:

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

img

  • Escolha o protocolo: Escolha o "Protocolo Geral".
  • Endereço do serviço: O protocolo geral é essencialmente um serviço RPC Por isso, é necessário especificar claramente o endereço do serviço, a porta, quando configurar o exchange.托管者上运行的策略实例 -> 通用协议程序No processo, o administrador só sabe onde acessar o protocolo geral. Por exemplo:http://127.0.0.1:6666/OKXOs servidores geralmente executam os programas do protocolo geral no mesmo dispositivo (servidor), então o endereço do serviço é escrito no local (localhost) e o porto é usado em um sistema não ocupado.
  • Chave de acesso:托管者上运行的策略实例 -> 通用协议程序O processo é executado através de uma rede de computadores, que transmite a informação de configuração do exchange.
  • Chave secreta:托管者上运行的策略实例 -> 通用协议程序O processo é executado através de uma rede de computadores, que transmite a informação de configuração do exchange.
  • As pessoas estão a morrer. É o nome dado a um objeto de uma bolsa de valores num inventor de uma plataforma de negociação quantitativa.

A imagem abaixo mostra a configuração do plugin OKX divulgado no artigo:

img

Informações sobre a configuração da chave secreta da OKX:

accessKey:  accesskey123    // accesskey123 这些并不是实际秘钥,仅仅是演示
secretKey:  secretkey123
passphrase: passphrase123

2  Implementar administradores e protocolos gerais (plugins)

  • 1o, administrador Para executar qualquer estratégia real em uma plataforma de negociação quantitativa do inventor, é necessário implantar um administrador, o qual pode ser consultado no tutorial da plataforma, que não será descrito aqui.

  • 2, Plugins de protocolo geral O servidor e o protocolo geral são geralmente implementados no mesmo dispositivo, e os programas de protocolo geral (serviços) podem usar qualquer linguagem para escrever projetos, sendo que este artigo é escrito em Python 3 e pode ser executado diretamente, como executar qualquer programa Python. É claro que o FMZ também tem suporte para executar programas Python, mas também pode usar o protocolo geral como um disco físico para fornecer aos inventores de plataformas de negociação quantitativa suporte para acesso a APIs de exchanges não encapsuladas. O protocolo geral é executado e a escuta é iniciada:http://127.0.0.1:6666O protocolo pode ser usado para definir um caminho específico, por exemplo./OKXO que é que ele está a fazer?

3, instância de política solicita a função API do FMZ

Quando a função API da plataforma FMZ é chamada na política, o protocolo geral recebe solicitações do administrador. Também é possível testar ferramentas de depuração da plataforma, como:

Página de ferramentas de depuração:

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

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

Chamadaexchange.GetTicker()Funções, protocolo geral recebe solicitações:

POST /OKX HTTP/1.1 
{
    "access_key":"xxx",
    "method":"ticker",
    "nonce":1730275031047002000,
    "params":{"symbol":"LTC_USDT"},
    "secret_key":"xxx"
}
  • access_key: A chave secreta da bolsa configurada no texto acima quando a plataforma "configure a troca"
  • secret_key: chave secreta da bolsa configurada quando a plataforma "configura a bolsa" no texto acima
  • método: relacionado com a chamada da interface na política, chamadaexchange.GetTicker()O que é isso?methodOu seja,ticker
  • Nonce: Tempo no qual ocorreu o pedido.
  • Parâmetros: Parâmetros relacionados quando uma chamada de interface ocorre na política, neste casoexchange.GetTicker()Os parâmetros que são usados são:{"symbol":"LTC_USDT"}

4. Procedimentos de protocolo geral para acessar a interface da bolsa

Quando um protocolo geral recebe uma solicitação do administrador, pode ser informado sobre as informações que a solicitação contém: a função API da plataforma (incluindo informações de parâmetros) e a chave secreta do exchange.

Com base nessas informações, o protocolo geral pode acessar a interface da bolsa, obter os dados necessários ou executar certas operações.

Normalmente, as interfaces de exchanges têm métodos como GET / POST / PUT / DELETE, divididos em interfaces públicas e privadas.

  • Interface pública: Interface sem necessidade de autenticação de assinatura, solicitada diretamente no processo de protocolo geral.
  • Interfaces privadas: Interfaces que exigem verificação de assinatura, que exigem a implementação de assinaturas em procedimentos de protocolo geral e, em seguida, solicitam as interfaces API dessas exchanges.

O processo de protocolo geral recebe dados de resposta da interface do exchange para processá-los, formando os dados esperados pelo administrador (descrição abaixo). Referência para a OKX Exchange, uma implementação da classe CustomProtocolOKX no paradigma de protocolo geral do PythonGetTickerGetAccountFunções iguais.

5. O protocolo geral responde aos administradores

Quando um programa de protocolo geral acessa a interface API de uma bolsa, executa certas operações ou obtém certos dados, precisa dar feedback aos administradores.

Os dados de feedback para os administradores variam de acordo com a interface em que a política é chamada, divididos em duas categorias:

  • O protocolo geral chamou a interface da bolsa com sucesso:

    {
        "data": null,  // "data" can be of any type 
        "raw": null    // "raw" can be of any type 
    }
    
    • data: estrutura específica do campo e solicitações recebidas pelo protocolo geralmethodRelacionado, a estrutura de dados utilizada para construir a função final de retorno da API da plataforma FMZ, listando todas as interfaces abaixo.
    • raw: Este campo pode transmitir dados primários de respostas de interfaces de API da bolsa, como por exemploexchange.GetTicker()A estrutura do ticker que a função retorna, registrada no campo Info da estrutura do ticker é:rawQuadro edataOs dados dos campos; funções da API da plataforma, algumas não precisam deste dado.
  • Falha no protocolo geral para chamar a interface da bolsa (errores de negócios, erros de rede, etc.)

    {
        "error": ""    // "error" contains an error message as a string
    }
    
    • error: mensagem de erro, que aparece no registro de erros da área do diário de páginas do disco físico da plataforma FMZ, ferramentas de depuração, etc.;

Os dados de resposta do protocolo geral recebidos pelo programa de estratégia de demonstração:

// FMZ平台的调试工具中测试
function main() {
    Log(exchange.GetTicker("USDT"))       // 交易对不完整,缺少BaseCurrency部分,需要通用协议插件程序返回报错信息: {"error": "..."}
    Log(exchange.GetTicker("LTC_USDT"))
}

img

6. Convenções de estrutura de dados no protocolo geral

O conteúdo acima é um breve processo do programa de protocolo geral envolvido no acesso à API de câmbio (FMZ não encapsulado), que explica apenas as chamadas no FMZ.exchange.GetTicker()O processo de função. A seguir, detalhes sobre a interação de todas as funções da API da plataforma serão descritos em detalhes.

A plataforma encapsula funções comuns de todas as exchanges, todas unificadas em uma função, como a função GetTicker, que solicita informações de mercado de uma variedade atual, que é basicamente uma API disponível em todas as exchanges. Assim, quando o administrador acessa a interface API embalada pela plataforma no exemplo da política, ele envia um pedido ao plugin "Protocolo Geral" (mencionado acima):

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

Quando as funções de API que são encapsuladas em diferentes plataformas de inventores são chamadas em políticas (por exemplo, GetTicker), o formato de solicitação enviado pelo administrador ao protocolo geral também será diferente.methodeparamsO protocolo é uma ferramenta de comunicação que permite a criação de um protocolo geral.methodO conteúdo é apenas para executar uma operação específica.

Bolsa de Valores

Por exemplo, o par de transações atual é:ETH_USDTOs dados que os administradores esperam que o protocolo geral responda são escritos principalmente no campo de dados, mas também é possível adicionar um campo raw para registrar os dados originais da interface do exchange.

  • GetTicker

    • Método de campo: ticker ticker
    • O campo de params:
      {"symbol":"ETH_USDT"}
      
    • Os dados que os administradores esperam que o protocolo genérico responda:
      {
          "data": {
              "symbol": "ETH_USDT",      // 对应GetTicker函数返回的Ticker结构中的Symbol字段
              "buy": "2922.18",          // ...对应Buy字段
              "sell": "2922.19", 
              "high": "2955", 
              "low": "2775.15", 
              "open": "2787.72", 
              "last": "2922.18", 
              "vol": "249400.888156", 
              "time": "1731028903911"
          },
          "raw": {}                      // 可以增加一个raw字段记录交易所API接口应答的原始数据
      }
      
  • GetDepth

    • Método de campo: depth
    • O campo de params:
      {"limit":"30","symbol":"ETH_USDT"}
      
    • Os dados que os administradores esperam que o protocolo genérico responda:
      {
          "data" : {
              "time" : 1500793319499,
              "asks" : [
                  [1000, 0.5], [1001, 0.23], [1004, 2.1]
                  // ... 
              ],
              "bids" : [
                  [999, 0.25], [998, 0.8], [995, 1.4]
                  // ... 
              ]
          }
      }
      
  • GetTrades

    • Método campo: trades
    • O campo de params:
      {"symbol":"eth_usdt"}
      
    • Os dados que os administradores esperam que o protocolo genérico responda:
      { 
          "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

    • Método de campo:
    • O campo de params:
      {
          "limit":"500",
          "period":"60",          // 60分钟
          "symbol":"ETH_USDT"
      }
      
    • Os dados que os administradores esperam que o protocolo genérico responda:
      {
          "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 ser realizado

    • método campo:""
    • O campo de params:
      {}
      
    • Os dados que os administradores esperam que o protocolo genérico responda:
      {}
      
  • GetTickersA ser realizado

    • método campo:""
    • O campo de params:
      {}
      
    • Os dados que os administradores esperam que o protocolo genérico responda:
      {}
      
  • GetAccount

    • Método campo: accounts
    • O campo de params:
      {}
      
    • Os dados que os administradores esperam que o protocolo genérico responda:
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • GetAssets

    • Método de campo: Método dos ativos
    • O campo de params:
      {}
      
    • Os dados que os administradores esperam que o protocolo genérico responda:
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"},
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • Criar Ordem / Comprar / Vender

    • Método de campo:
    • O campo de params:
      {"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
      
    • Os dados que os administradores esperam que o protocolo genérico responda:
      {
          "data": {
              "id": "BTC-USDT,123456"
          }
      }
      
  • GetOrders

    • Método campo: orders
    • O campo de params:
      {"symbol":"ETH_USDT"}
      
    • Os dados que os administradores esperam que o protocolo genérico responda:
      {
          "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

    • Método de campo: → ordem →
    • O campo de params:
      {
          "id":"ETH-USDT,123456",       // 策略中调用:exchange.GetOrder("ETH-USDT,123456")
          "symbol":"ETH_USDT"
      }
      
    • Os dados que os administradores esperam que o protocolo genérico responda:
      { 
          "data": {
              "id": "ETH-USDT,123456",
              "symbol": "ETH_USDT"
              "amount": 0.15,
              "price": 1002,
              "status": "pending",    // "pending", "pre-submitted", "submitting", "submitted", "partial-filled", "filled", "closed", "finished", "partial-canceled", "canceled"
              "deal_amount": 0,
              "type": "buy",          // "buy"、"sell"
              "avg_price": 0,         // 如果交易所没有提供,在处理时可以赋值为0
          }
      }
      
  • Ordens do GetHistory

    • Campo de método: →historyorders →
    • O campo de params:
      {"limit":0,"since":0,"symbol":"ETH_USDT"}
      
    • Os dados que os administradores esperam que o protocolo genérico responda:
      {
          "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

    • método campo: → cancelar →
    • O campo de params:
      {"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
      
    • Os dados que os administradores esperam que o protocolo genérico responda:
      {
          "data": true    // 只要该JSON中没有error字段,都默认为撤单成功
      }
      
  • O

    exchange.IO函数用于直接访问交易所接口,例如我们以GET /api/v5/trade/orders-pending, 参数:instType=SPOT,instId=ETH-USDTPor exemplo:

    // 策略实例中调用
    exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")
    
    • Método campo:"__api_/api/v5/trade/orders-pending"O conteúdo do campo de método começa com __api_, o que significa que isso foi desencadeado por uma chamada para a função exchange.IO no exemplo da política.
    • O campo de params:
      {"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
      
    • Os dados que os administradores esperam que o protocolo genérico responda:
      {
          "data": {"code": "0", "data": [], "msg": ""}    // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
      }
      
  • Outros Outras funções da API de plataforma de inventores usadas em exemplos de estratégia, como:exchange.Go()exchange.GetRawJSON()A função não precisa ser encapsulada, o modo de chamada, a função não é alterada.

Bolsa de futuros

Além de suportar funções de todas as exchanges em tempo real, existem algumas funções de API exclusivas de exchanges de futuros.

A ser realizado

  • GetPositions
  • SetMarginLevel
  • GetFundings

Exemplos de protocolo geral para versões do Python

O REST General Protocol plug-in acessa a interface REST API da OKX Exchange e é envelopado como um objeto de troca em tempo real. Implementação de um envelope de dados de solicitação e resposta em uma interface pública. A implementação de uma assinatura de interface privada, requisição e envelopamento de dados de resposta. Este exemplo é baseado na aprendizagem de testes, o restante das interfaces usa dados simulados para responder diretamente ao administrador 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'
        if httpMethod == "GET":
            jsonStr = json.dumps(params, separators=(',', ':')) if len(params) > 0 else ""
        else:
            jsonStr = json.dumps(params, separators=(',', ':')) if len(params) > 0 else "{}"
        message = f'{strTime}{httpMethod}{path}{jsonStr}'
        if httpMethod == "GET" and query != "":
            message = f'{strTime}{httpMethod}{path}?{query}{jsonStr}'
        mac = hmac.new(bytes(self._secretKey, encoding='utf8'), bytes(message, encoding='utf-8'), digestmod='sha256')
        signature = base64.b64encode(mac.digest())
        
        headers = {}
        if self._x_simulated_trading == 1:
            headers["x-simulated-trading"] = str(self._x_simulated_trading)
        headers["OK-ACCESS-KEY"] = self._accessKey
        headers["OK-ACCESS-PASSPHRASE"] = self._passphrase
        headers["OK-ACCESS-TIMESTAMP"] = strTime        
        headers["OK-ACCESS-SIGN"] = signature
        return self._httpRequest(httpMethod, path, query, params, headers)

    
    # Encapsulates requests to the exchange API.
    def GetTicker(self, symbol):
        """
        GET /api/v5/market/ticker , param: instId 
        """

        baseCurrency, quoteCurrency = self.getCurrencys(symbol)
        if baseCurrency == "" or quoteCurrency == "":
            return {"error": "invalid symbol"}

        path = "/api/v5/market/ticker"
        query = f'instId={baseCurrency}-{quoteCurrency}'
        data = self.callUnsignedAPI("GET", path, query=query)
        if "error" in data.keys() and "data" not in data.keys():
            return data

        ret_data = {}
        if data["code"] != "0" or not isinstance(data["data"], list):
            return {"error": json.dumps(data, ensure_ascii=False)}
        for tick in data["data"]:
            if not all(k in tick for k in ("instId", "bidPx", "askPx", "high24h", "low24h", "vol24h", "ts")):
                return {"error": json.dumps(data, ensure_ascii=False)}

            ret_data["symbol"] = self.getSymbol(tick["instId"])
            ret_data["buy"] = tick["bidPx"]
            ret_data["sell"] = tick["askPx"]
            ret_data["high"] = tick["high24h"]
            ret_data["low"] = tick["low24h"]
            ret_data["open"] = tick["open24h"]
            ret_data["last"] = tick["last"]
            ret_data["vol"] = tick["vol24h"]
            ret_data["time"] = tick["ts"]

        return {"data": ret_data, "raw": data}


    def GetDepth(self, symbol):
        """
        TODO: Implementation code
        """
        
        # Mock data for testing.
        ret_data = {            
            "time" : 1500793319499,
            "asks" : [
                [1000, 0.5], [1001, 0.23], [1004, 2.1]
            ],
            "bids" : [
                [999, 0.25], [998, 0.8], [995, 1.4]
            ]            
        }
        
        return {"data": ret_data}


    def GetTrades(self, symbol):
        """
        TODO: Implementation code
        """

        # Mock data for testing.
        ret_data = [
            {
                "id": 12232153,
                "time" : 1529919412968,
                "price": 1000,
                "amount": 0.5,
                "type": "buy",
            }, {
                "id": 12545664,
                "time" : 1529919412900,
                "price": 1001,
                "amount": 1,
                "type": "sell",
            }
        ]

        return {"data": ret_data}


    def GetRecords(self, symbol, period, limit):
        """
        TODO: Implementation code
        """

        # Mock data for testing.
        ret_data = [
            [1500793319, 1.1, 2.2, 3.3, 4.4, 5.5],
            [1500793259, 1.01, 2.02, 3.03, 4.04, 5.05],
        ]

        return {"data": ret_data}


    def GetMarkets(self):
        """
        TODO: Implementation code
        """

        ret_data = {}

        return {"data": ret_data}


    def GetTickers(self):
        """
        TODO: Implementation code
        """

        ret_data = {}

        return {"data": ret_data}


    def GetAccount(self):
        """
        GET /api/v5/account/balance
        """

        path = "/api/v5/account/balance"
        data = self.callSignedAPI("GET", path)

        ret_data = []
        if data["code"] != "0" or "data" not in data or not isinstance(data["data"], list):
            return {"error": json.dumps(data, ensure_ascii=False)}
        for ele in data["data"]:
            if "details" not in ele or not isinstance(ele["details"], list):
                return {"error": json.dumps(data, ensure_ascii=False)}
            for detail in ele["details"]:
                asset = {"currency": detail["ccy"], "free": detail["availEq"], "frozen": detail["ordFrozen"]}
                if detail["availEq"] == "":
                    asset["free"] = detail["availBal"]
                ret_data.append(asset)
        return {"data": ret_data, "raw": data}


    def GetAssets(self):
        """
        TODO: Implementation code
        """
        
        # Mock data for testing.
        ret_data = [
            {"currency": "TUSD", "free": "3000", "frozen": "0"},
            {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}
        ]

        return {"data": ret_data}


    def CreateOrder(self, symbol, side, price, amount):
        """
        TODO: Implementation code
        """
        
        # Mock data for testing.
        ret_data = {
            "id": "BTC-USDT,123456"
        }

        return {"data": ret_data}

    
    def GetOrders(self, symbol):
        """
        GET /api/v5/trade/orders-pending  instType SPOT instId  after limit
        """
        
        baseCurrency, quoteCurrency = self.getCurrencys(symbol)
        if baseCurrency == "" or quoteCurrency == "":
            return {"error": "invalid symbol"}

        path = "/api/v5/trade/orders-pending"
        after = ""
        limit = 100

        ret_data = []
        while True:
            query = f"instType=SPOT&instId={baseCurrency}-{quoteCurrency}&limit={limit}"
            if after != "":
                query = f"instType=SPOT&instId={baseCurrency}-{quoteCurrency}&limit={limit}&after={after}"
        
            data = self.callSignedAPI("GET", path, query=query)
            
            if data["code"] != "0" or not isinstance(data["data"], list):
                return {"error": json.dumps(data, ensure_ascii=False)}
            for ele in data["data"]:
                order = {}

                order["id"] = f'{ele["instId"]},{ele["ordId"]}'
                order["symbol"] = f'{baseCurrency}-{quoteCurrency}'
                order["amount"] = ele["sz"]
                order["price"] = ele["px"]
                order["deal_amount"] = ele["accFillSz"]
                order["avg_price"] = 0 if ele["avgPx"] == "" else ele["avgPx"]
                order["type"] = "buy" if ele["side"] == "buy" else "sell"
                order["state"] = "pending"

                ret_data.append(order)
                after = ele["ordId"]

            if len(data["data"]) < limit:
                break

        return {"data": ret_data}


    def GetOrder(self, orderId):
        """
        TODO: Implementation code
        """
        
        # Mock data for testing.
        ret_data = {
            "id": "ETH-USDT,123456",
            "symbol": "ETH_USDT",
            "amount": 0.15,
            "price": 1002,
            "status": "pending",
            "deal_amount": 0,
            "type": "buy",
            "avg_price": 0,
        }

        return {"data": ret_data}


    def GetHistoryOrders(self, symbol, since, limit):
        """
        TODO: Implementation code
        """

        # Mock data for testing.
        ret_data = [
            {
                "id": "ETH-USDT,123456",
                "symbol": "ETH_USDT",
                "amount": 0.25,
                "price": 1005,
                "deal_amount": 0,
                "avg_price": 1000,
                "type": "buy",
                "status": "filled"
            }
        ]

        return {"data": ret_data}


    def CancelOrder(self, orderId):
        """
        TODO: Implementation code
        """

        # Mock data for testing.
        ret_data = True

        return {"data": ret_data}


    def IO(self, httpMethod, path, params={}):
        if httpMethod == "GET":
            query = urllib.parse.urlencode(params)
            data = self.callSignedAPI(httpMethod, path, query=query)
        else:
            data = self.callSignedAPI(httpMethod, path, params=params)
        
        if data["code"] != "0":
            return {"error": json.dumps(data, ensure_ascii=False)}

        return {"data": data}


class HttpServer(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        self.request_body = None
        self.request_path = None
        super().__init__(*args, **kwargs)


    def log_message(self, format, *args):
        return 


    def _sendResponse(self, body):
        self.send_response(200)
        self.send_header('Content-type', 'application/json; charset=utf-8')
        self.end_headers()
        self.wfile.write(json.dumps(body).encode('utf-8'))


    def do_GET(self):
        # The FMZ.COM custom protocol only send GET method request
        self._sendResponse({"error": "not support GET method."})


    def do_POST(self):
        """
        Returns:
            json: success, {"data": ...}
            json: error,   {"error": ...}
        """

        contentLen = int(self.headers['Content-Length'])
        self.request_body = self.rfile.read(contentLen)
        self.request_path = self.path
        exName = self.request_path.lstrip("/")

        # Print the request received from the FMZ.COM robot
        print(f"--------- request received from the FMZ.COM robot: --------- \n {self.requestline} | Body: {self.request_body} | Headers: {self.headers} \n")

        try:
            data = json.loads(self.request_body)
        except json.JSONDecodeError:
            data = {"error": self.request_body.decode('utf-8')}
            self._sendResponse(data)
            return 

        # fault tolerant
        if not all(k in data for k in ("access_key", "secret_key", "method", "params")):
            data = {"error": "missing required parameters"}
            self._sendResponse(data)
            return

        respData = {}
        accessKey = data["access_key"]
        secretKey = data["secret_key"]
        method = data["method"]
        params = data["params"]
        exchange = ProtocolFactory.createExWrapper("https://www.okx.com", accessKey, secretKey, exName)

        if method == "ticker":
            symbol = str(params["symbol"]).upper()
            respData = exchange.GetTicker(symbol)
        elif method == "depth":
            symbol = str(params["symbol"]).upper()
            respData = exchange.GetDepth(symbol)
        elif method == "trades":
            symbol = str(params["symbol"]).upper()
            respData = exchange.GetTrades(symbol)
        elif method == "records":
            symbol = str(params["symbol"]).upper()
            period = int(params["period"])
            limit = int(params["limit"])
            respData = exchange.GetRecords(symbol, period, limit)
        elif method == "accounts":
            respData = exchange.GetAccount()
        elif method == "assets":
            respData = exchange.GetAssets()
        elif method == "trade":
            amount = float(params["amount"])
            price = float(params["price"])
            symbol = str(params["symbol"])
            tradeType = str(params["type"])
            respData = exchange.CreateOrder(symbol, tradeType, price, amount)
        elif method == "orders":
            symbol = str(params["symbol"]).upper()
            respData = exchange.GetOrders(symbol)
        elif method == "order":
            orderId = str(params["id"])
            respData = exchange.GetOrder(orderId)
        elif method == "historyorders":
            symbol = str(params["symbol"])
            since = int(params["since"])
            limit = int(params["limit"])
            respData = exchange.GetHistoryOrders(symbol, since, limit)
        elif method == "cancel":
            orderId = str(params["id"])
            respData = exchange.CancelOrder(orderId)
        elif method[:6] == "__api_":
            respData = exchange.IO(self.headers["Http-Method"], method[6:], params)
        else:
            respData = {"error": f'invalid method: {method}'}

        # Print the response to send to FMZ.COM robot
        print(f"response to send to FMZ.COM robot: {respData} \n")

        self._sendResponse(respData)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Run a FMZ.COM custom protocol plugin.")
    parser.add_argument("--port", type=int, default=6666, help="Port to run the server on.")
    parser.add_argument("--address", type=str, default="localhost", help="Address to bind the server to.")
    args = parser.parse_args() 

    with socketserver.TCPServer((args.address, args.port), HttpServer) as httpd:
        print(f"running... {args.address}:{args.port}", "\n")
        httpd.serve_forever()

Mais.