En la carga de los recursos... Cargando...

Guía de acceso a las plataformas de intercambio cuantitativo de los inventores

El autor:Los inventores cuantifican - sueños pequeños, Creado: 2024-10-29 14:37:56, Actualizado: 2024-11-12 21:58:55

[TOC] ¿Qué quieres decir?

img

Las plataformas de intercambio cuantificadas de Inventor soportan a numerosos intercambios de criptomonedas y están envueltas en los principales intercambios en el mercado. Sin embargo, todavía hay muchos intercambios que no están envueltos, y los usuarios que necesitan usarlos pueden acceder a ellos a través de un protocolo general cuantificado por Inventor.RESTacuerdo oEl número deTambién se puede acceder a las plataformas del protocolo.

Este artículo se centraráRESTEl protocolo de acceso, por ejemplo, explica cómo usar el protocolo general de la plataforma de comercio cuantitativo de los inventores para envasar y acceder a la API de OKX.

  • El proceso de trabajo del protocolo general es el siguiente: Proceso de solicitud: Instancias de políticas que se ejecutan en el administrador -> Procedimiento de protocolo general -> API de intercambio Proceso de respuesta: API del exchange -> Procedimiento de protocolo general -> Instancias de políticas que se ejecutan en el administrador

1. Configurar el intercambio

La página donde los inventores configuran las plataformas de intercambio cuantitativas:

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

img

  • Seleccionar el protocolo: Seleccione "Protocolo general".
  • Dirección del servicio: El protocolo general es esencialmente un servicio RPC Por lo tanto, es necesario especificar claramente la dirección del servicio, el puerto cuando se configura el intercambio.托管者上运行的策略实例 -> 通用协议程序En el proceso, el administrador sabe dónde acceder al protocolo general. Por ejemplo:http://127.0.0.1:6666/OKXPor lo general, los programas de protocolo general se ejecutan en el mismo equipo (servidor) que los hosts, por lo que la dirección del servicio se escribe en el localhost y el puerto se utiliza en un sistema que no está ocupado.
  • Clave de acceso:托管者上运行的策略实例 -> 通用协议程序Durante el proceso, la información de configuración del exchange que se transmite.
  • Clave secreta:托管者上运行的策略实例 -> 通用协议程序Durante el proceso, la información de configuración del exchange que se transmite.
  • Las mujeres son las que más sufren. Etiquetas de objetos de intercambio en plataformas de intercambio cuantificadas de inventores, utilizadas para identificar un objeto de intercambio.

La configuración del complemento OKX que se muestra en el artículo:

img

La información de configuración de la clave secreta de OKX:

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

2. Implementar hosts y protocolos generales (plugins)

  • 1 El administrador Para ejecutar cualquier estrategia en la plataforma de comercio cuantitativo de los inventores, se debe implementar un administrador, el cual se puede consultar en los tutoriales de la plataforma, que no se describen aquí.

  • 2, Programa de protocolo general (plugin) El administrador y el protocolo general se implementan generalmente en el mismo dispositivo, y los programas de protocolo general (servicios) pueden escribir diseños en cualquier lengua, y este artículo está escrito en Python 3. Por supuesto, en FMZ también se puede ejecutar un programa de Python, pero también se puede ejecutar este protocolo general como un disco real para proporcionar a los inventores de plataformas de intercambio cuantitativas soporte para el acceso a las API de intercambio sin envasar. Después de que el protocolo general se haya ejecutado, comienza a escuchar:http://127.0.0.1:6666En el protocolo general, se puede usar un camino específico, por ejemplo./OKXEl proceso se lleva a cabo.

3, la aplicación de la política de instancia para solicitar la función API de FMZ

Cuando se llama a la función API de la plataforma (FMZ) en la política, el protocolo generalista recibe las solicitudes de los hosts. También se pueden probar herramientas de depuración de la plataforma, por ejemplo:

La página de herramientas de depuración:

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

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

Llamadoexchange.GetTicker()Función, el protocolo general ha recibido la solicitud:

POST /OKX HTTP/1.1 
{
    "access_key":"xxx",
    "method":"ticker",
    "nonce":1730275031047002000,
    "params":{"symbol":"LTC_USDT"},
    "secret_key":"xxx"
}
  • access_key: clave secreta de intercambio configurada en el texto anterior cuando la plataforma "configura el intercambio"
  • secret_key: clave secreta de intercambio configurada en la plataforma "configuración de los intercambios" en el texto anterior
  • método: relacionado con la llamada de la interfaz en la política, llamadaexchange.GetTicker()¿Qué es lo que está pasando?methodEs decir,ticker
  • Nonce: el momento en el que ocurrió la solicitud.
  • Parámetros: Parámetros relacionados con la llamada de interfaz en la política, en este casoexchange.GetTicker()Cuando se llama, los parámetros correspondientes son:{"symbol":"LTC_USDT"}

4. Procedimiento de protocolo general para acceder a las interfaces de los intercambios

Cuando un protocolo generalista recibe una solicitud de un administrador, se puede saber, según la información que lleva la solicitud: la función API de la plataforma solicitada por la política (incluida la información de parámetros) o la clave secreta de la bolsa.

Con base en esta información, el protocolo generalista puede acceder a la interfaz de la bolsa, obtener los datos requeridos o realizar ciertas operaciones.

Normalmente, las interfaces de los intercambios tienen métodos como GET / POST / PUT / DELETE, divididos en interfaces públicas y privadas.

  • Interfaz pública: Interfaz sin necesidad de autenticación de firma, solicitada directamente en el protocolo general.
  • Interfaces privadas: Interfaces que requieren la autenticación de la firma, que requieren la firma en el protocolo general, y luego solicitan las interfaces API de estos intercambios.

El protocolo general recibe los datos de respuesta de la interfaz de intercambio para su posterior procesamiento, lo que constituye los datos esperados por el administrador. Para referirse a OKX Exchange, en el ejemplo de protocolo general de Python en la implementación de la clase CustomProtocolOKXGetTickerGetAccountEs una función igual.

5. Los protocolos generales responden a los administradores

Cuando un programa de protocolo general accede a la interfaz API de un exchange, realiza ciertas operaciones o obtiene ciertos datos, necesita dar feedback al administrador.

Los datos de respuesta a los administradores varían según la interfaz a la que se llama la política, y se dividen primero en dos grandes categorías:

  • El protocolo general ha llamado a la interfaz de intercambio con éxito:

    {
        "data": null,  // "data" can be of any type 
        "raw": null    // "raw" can be of any type 
    }
    
    • data: la estructura específica del campo y las solicitudes recibidas por el protocolo generalmethodLas estructuras de datos que se utilizan para construir las funciones de la API de la plataforma FMZ, que finalmente se devuelven, se enumeran a continuación.
    • raw: este campo puede transmitir datos primarios de las respuestas de las interfaces API de los intercambios, por ejemploexchange.GetTicker()La estructura de ticker que devuelve la función, registrada en el campo Info de la estructura de ticker esrawLos campos ydataLos datos de los campos; las funciones API de la plataforma, algunas no requieren estos datos.
  • Fallo en la interfaz de intercambio de llamadas del protocolo general (errores de operación, errores de red, etc.)

    {
        "error": ""    // "error" contains an error message as a string
    }
    
    • error: Información de error que aparece en el registro de error de las áreas de registro de páginas como el disco físico de la plataforma FMZ, herramientas de depuración y otros.

Los datos de respuesta del protocolo general recibidos por el programa de estrategia de demostración:

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

img

6. Los acuerdos de estructura de datos en el protocolo general

Lo anterior es un breve proceso en el que los protocolos generales participan en el acceso a la API de los intercambios (FMZ no envuelto), que solo explica las llamadas en la herramienta de depuración de la plataforma (FMZ).exchange.GetTicker()Proceso de la función. A continuación se explican los detalles de la interacción de todas las funciones de la API de la plataforma.

La plataforma envuelve las funciones comunes de los distintos intercambios, que se unifican en una función, como la función GetTicker, que solicita información de mercado de una variedad actual, que es básicamente una API disponible en todos los intercambios. Por lo tanto, cuando el administrador accede a la interfaz API envuelta en la plataforma en el ejemplo de la política, envía una solicitud al plugin "Protocolo General" (como se mencionó anteriormente):

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

Cuando se llama una función de API envuelta en una plataforma de diferentes inventores en la política (por ejemplo, GetTicker), el formato de la solicitud que el anfitrión envía al protocolo general también será diferente.methodyparamsLos protocolos generales se diseñan de acuerdo con las reglas del protocolo.methodEl contenido de la interfaz puede ser utilizado para realizar operaciones específicas.

Las bolsas de valores

Por ejemplo, el par de transacciones en curso es:ETH_USDTLos administradores esperan que los datos que responde el protocolo general se escriban principalmente en el campo de datos, y también se puede agregar un campo raw para registrar los datos originales de la interfaz de intercambio.

  • ¿ Qué haces?

    • El campo de método:
    • El campo de params:
      {"symbol":"ETH_USDT"}
      
    • Los administradores esperan que el protocolo general responda a los siguientes datos:
      {
          "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接口应答的原始数据
      }
      
  • Obtener Profundidad

    • El campo de método:
    • El campo de params:
      {"limit":"30","symbol":"ETH_USDT"}
      
    • Los administradores esperan que el protocolo general responda a los siguientes datos:
      {
          "data" : {
              "time" : 1500793319499,
              "asks" : [
                  [1000, 0.5], [1001, 0.23], [1004, 2.1]
                  // ... 
              ],
              "bids" : [
                  [999, 0.25], [998, 0.8], [995, 1.4]
                  // ... 
              ]
          }
      }
      
  • Obtener Trades

    • El campo de método:
    • El campo de params:
      {"symbol":"eth_usdt"}
      
    • Los administradores esperan que el protocolo general responda a los siguientes datos:
      { 
          "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",
              }
              // ...
          ]
      }
      
  • Obtener registros

    • El método de los campos:
    • El campo de params:
      {
          "limit":"500",
          "period":"60",          // 60分钟
          "symbol":"ETH_USDT"
      }
      
    • Los administradores esperan que el protocolo general responda a los siguientes datos:
      {
          "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],
                  // ...
          ]
      }
      
  • Obtener MercadosLo que está por hacerse

    • El campo de método:""
    • El campo de params:
      {}
      
    • Los administradores esperan que el protocolo general responda a los siguientes datos:
      {}
      
  • Los GetTickersLo que está por hacerse

    • El campo de método:""
    • El campo de params:
      {}
      
    • Los administradores esperan que el protocolo general responda a los siguientes datos:
      {}
      
  • Obtener Cuenta

    • Método para el campo: accounts
    • El campo de params:
      {}
      
    • Los administradores esperan que el protocolo general responda a los siguientes datos:
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • Obtener Activos

    • El campo de método:
    • El campo de params:
      {}
      
    • Los administradores esperan que el protocolo general responda a los siguientes datos:
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"},
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • Crear Orden / Comprar / Vender

    • El campo de método:
    • El campo de params:
      {"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
      
    • Los administradores esperan que el protocolo general responda a los siguientes datos:
      {
          "data": {
              "id": "BTC-USDT,123456"
          }
      }
      
  • Obtener órdenes

    • El campo de método:orders
    • El campo de params:
      {"symbol":"ETH_USDT"}
      
    • Los administradores esperan que el protocolo general responda a los siguientes datos:
      {
          "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"
              }, 
              // ...
          ]
      }
      
  • Obtener orden

    • El campo de método:
    • El campo de params:
      {
          "id":"ETH-USDT,123456",       // 策略中调用:exchange.GetOrder("ETH-USDT,123456")
          "symbol":"ETH_USDT"
      }
      
    • Los administradores esperan que el protocolo general responda a los siguientes datos:
      { 
          "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
          }
      }
      
  • Obtener órdenes de historial

    • El campo de método: → historia de órdenes →
    • El campo de params:
      {"limit":0,"since":0,"symbol":"ETH_USDT"}
      
    • Los administradores esperan que el protocolo general responda a los siguientes datos:
      {
          "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 el pedido

    • El campo de método: cancelar
    • El campo de params:
      {"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
      
    • Los administradores esperan que el protocolo general responda a los siguientes datos:
      {
          "data": true    // 只要该JSON中没有error字段,都默认为撤单成功
      }
      
  • - ¿ Qué?

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

    // 策略实例中调用
    exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")
    
    • El campo de método:"__api_/api/v5/trade/orders-pending"El contenido del campo de método comienza con __api_, lo que significa que esto fue provocado por una llamada a la función exchange.IO en la instancia de la política.
    • El campo de params:
      {"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
      
    • Los administradores esperan que el protocolo general responda a los siguientes datos:
      {
          "data": {"code": "0", "data": [], "msg": ""}    // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
      }
      
  • Otros Otras funciones de la API de la plataforma de inventores utilizadas en el ejemplo de la estrategia, por ejemplo:exchange.Go()exchange.GetRawJSON()Las funciones, por ejemplo, no requieren envasado, la forma de llamar, la función no cambia.

Las bolsas de futuros

Además de las funciones de todos los intercambios de futuros, los intercambios de futuros también tienen funciones de API específicas.

Lo que está por hacerse

  • ObtenerPosiciones
  • Establecer el nivel de margen
  • Obtener fondos

Ejemplos de protocolo general de versiones de Python

El protocolo general REST se conecta a la interfaz REST API de OKX y se envuelve como un objeto de intercambio en tiempo real. Implementar un envase de datos de petición y respuesta en una interfaz pública. Implementación de una interfaz privada para la firma, solicitud y respuesta de datos envueltos. Este ejemplo está basado en el aprendizaje de pruebas, mientras que el resto de las interfaces utilizan datos de simulación para responder directamente a los administradores para realizar pruebas.

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

Más.