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

Guía de acceso al protocolo personalizado de la plataforma de negociación cuántica FMZ

El autor:FMZ~Lydia, Creado: 2024-11-08 16:37:25, Actualizado: 2024-11-08 16:38:52

Nosotros FMZ Quant Trading Platform soporta muchos intercambios de criptomonedas y encapsula los intercambios convencionales en el mercado. Sin embargo, todavía hay muchos intercambios que no están encapsulados. Para los usuarios que necesitan usar estos intercambios, pueden acceder a ellos a través del Protocolo Custom de FMZ Quant.RESTel protocolo oEl número deEl protocolo también puede ser accedido.

En este artículo seRESTProtocolo de acceso como ejemplo para explicar cómo usar el protocolo personalizado de la Plataforma de Comercio Cuántico FMZ para encapsular y acceder a la API del intercambio OKX.

  • El flujo de trabajo del protocolo personalizado es: Proceso de solicitud: Instancia de estrategia que se ejecuta en el docker -> Programa de protocolo personalizado -> Exchange API Proceso de respuesta: Exchange API -> Programa de protocolo personalizado -> Instancia de estrategia ejecutada en el docker

1. Configurar el intercambio

La página para configurar el intercambio en la plataforma de negociación de FMZ Quant:

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

img

  • Seleccione el protocolo: Seleccione el protocolo personalizado.
  • Dirección del servicio: El programa de protocolo personalizado es esencialmente un servicio RPC. Por lo tanto, es necesario especificar claramente la dirección del servicio y el puerto al configurar el intercambio.Strategy instance running on the docker -> Custom protocol program- ¿ Por qué? Por ejemplo:http://127.0.0.1:6666/OKX, el programa de protocolo personalizado y el docker generalmente se ejecutan en el mismo dispositivo (servidor), por lo que la dirección del servicio se escribe como la máquina local (localhost), y el puerto se puede usar como un puerto que no está ocupado por el sistema.
  • Clave de acceso: La información de configuración de intercambio transmitida durante el procesoStrategy instance running on the docker -> Custom protocol program.
  • Clave secreta: La información de configuración de intercambio transmitida durante el procesoStrategy instance running on the docker -> Custom protocol program.
  • - ¿ Qué es eso? La etiqueta del objeto de intercambio en la plataforma de negociación cuántica FMZ, utilizada para identificar un determinado objeto de intercambio.

La captura de pantalla de la configuración del plug-in OKX revelada en el artículo es la siguiente:

img

Información de configuración de clave secreta de intercambio OKX:

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

2. Despliegue del Docker y el Programa de Protocolo Personalizado (plugin)

    1. El muelle Para ejecutar cualquier estrategia en la plataforma de comercio de FMZ Quant, se debe implementar un docker. Para obtener detalles sobre cómo implementar un docker, consulte el tutorial de la plataforma.
    1. Programa de protocolo personalizado (plugin) Docker y el protocolo personalizado generalmente se implementan en el mismo dispositivo. El programa de protocolo (servicio) personalizado se puede escribir en cualquier idioma. Este artículo está escrito en Python3. Al igual que ejecutar cualquier programa Python, se puede ejecutar directamente (hacer varias configuraciones del entorno Python de antemano). Por supuesto, FMZ también admite ejecutar programas de Python, y también puede ejecutar este protocolo personalizado como un comercio en vivo para proporcionar a la Plataforma de Comercio Cuántico FMZ con soporte para el acceso de API de intercambio desempaquetado. Después de ejecutar el programa de protocolo personalizado, comienza a escuchar:http://127.0.0.1:6666El programa de protocolo personalizado puede procesar rutas específicas, tales como/OKX.

3. la estrategia de la instancia solicita FMZ API Función

Cuando se llama la función API de la plataforma (FMZ) en la estrategia, el programa de protocolo personalizado recibirá una solicitud del docker. También puede probarlo utilizando las herramientas de depuración de la plataforma, por ejemplo:

Página de herramientas de depuración:

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

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

Llamando a la funciónexchange.GetTicker(), el programa de protocolo personalizado recibe la solicitud:

POST /OKX HTTP/1.1 
{
    "access_key":"xxx",
    "method":"ticker",
    "nonce":1730275031047002000,
    "params":{"symbol":"LTC_USDT"},
    "secret_key":"xxx"
}
  • access_key: La clave de intercambio configurada en la plataforma Configure Exchange anterior
  • secret_key: La clave de intercambio configurada en la plataforma Configure Exchange anterior
  • método: relacionado con la interfaz de llamada en la estrategia, cuando se llamaexchange.GetTicker(), methodesticker.
  • noce: la fecha y hora en que se produjo la solicitud.
  • Parámetros relacionados con la llamada de interfaz en la estrategia, en este ejemplo, cuando se llamaexchange.GetTicker(), los parámetros correspondientes son:{"symbol":"LTC_USDT"}.

4. Acceso al programa de protocolo personalizado a la interfaz de intercambio

Cuando el programa de protocolo personalizado recibe una solicitud del docker, puede obtener información como la función API de la plataforma (incluida la información de parámetros) solicitada por la estrategia, la clave de intercambio, etc. basándose en la información contenida en la solicitud.

Sobre la base de esta información, el programa de protocolo personalizado puede acceder a la interfaz de intercambio para obtener los datos requeridos o realizar ciertas operaciones.

Por lo general, la interfaz de intercambio tiene métodos como GET/POST/PUT/DELETE, que se dividen en interfaz pública e interfaz privada.

  • Interfaz pública: una interfaz que no requiere la verificación de la firma y se solicita directamente en un programa de protocolo personalizado.
  • Interfaz privada: una interfaz que requiere la verificación de la firma. La firma debe implementarse en el programa de protocolo personalizado para solicitar la interfaz API de estos intercambios.

El programa de protocolo personalizado recibe los datos de respuesta de la interfaz de intercambio y los procesa para construir los datos esperados por el docker (descrito a continuación).GetTicker, GetAccounty otras funciones en la implementación de la clase CustomProtocolOKX en el ejemplo de protocolo personalizado de Python.

5. El programa de protocolo personalizado responde a los datos al Docker

Cuando el programa de protocolo personalizado accede a la interfaz API del intercambio, realiza ciertas operaciones u obtiene ciertos datos, necesita devolver los resultados al docker.

Los datos enviados al docker varían según la interfaz llamada por la estrategia, y primero se dividen en dos categorías:

  • El programa de protocolo personalizado llama la interfaz de intercambio con éxito:
{
    "data": null,  // "data" can be of any type 
    "raw": null    // "raw" can be of any type 
}

datos: La estructura específica de este campo está relacionada con lamethoden la solicitud recibida por el programa de protocolo personalizado, y se utiliza para construir la estructura de datos finalmente devuelta por la función API de la plataforma FMZ. raw: Este campo se puede utilizar para transmitir los datos en bruto de la respuesta de la interfaz de API de intercambio, como la estructura de ticker devuelta por elexchange.GetTicker()El campo Info de la estructura Ticker registra los datos de larawel campo y eldataEn la actualidad, la mayoría de las aplicaciones de la plataforma utilizan el campo de datos de la plataforma; algunas de las funciones API de la plataforma no necesitan estos datos.

  • El programa de protocolo personalizado no pudo llamar a la interfaz de intercambio (error de negocio, error de red, etc.)
{
    "error": ""    // "error" contains an error message as a string
}

error: información de error que se mostrará en el registro de errores en el área de registro de la plataforma (FMZ) de operaciones en vivo, herramienta de depuración y otras páginas.

Demuestra los datos de respuesta de protocolo personalizados recibidos por el programa de estrategia:

// 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. Acuerdo de estructura de datos en el protocolo de costumbre

Lo anterior es un breve proceso del programa de protocolo personalizado que participa en el acceso a la API de intercambio (FMZ desempaquetada).exchange.GetTicker()La siguiente explica en detalle los detalles de interacción de todas las funciones API de la plataforma.

La plataforma encapsula las funciones comunes de varios intercambios y las unifica en una determinada función, como la función GetTicker, que solicita la información actual del mercado de un determinado producto. Esta es una API que básicamente todos los intercambios tienen. Por lo tanto, al acceder a la interfaz API encapsulada por la plataforma en una instancia de estrategia, el docker enviará una solicitud al programa de complemento Custom Protocol (mencionado anteriormente):

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

Cuando se llaman diferentes funciones API encapsuladas de la plataforma FMZ en la estrategia (como GetTicker), el formato de solicitud enviado por el docker al protocolo personalizado también será diferente.methodyparams. Al diseñar un protocolo personalizado, realizar operaciones específicas según el contenido del método. Los siguientes son los escenarios de solicitud-respuesta para todas las interfaces.

Intercambio al contado

Por ejemplo, el par de operaciones actual es:ETH_USDTLos datos a los que el docker espera que responda el protocolo personalizado se escriben principalmente en el campo de datos, y también se puede agregar un campo sin procesar para registrar los datos originales de la interfaz de intercambio.

  • ¿ Qué haces?

campo de método: ticker campo de parámetros:

{"symbol":"ETH_USDT"}

Datos que el docker espera en la respuesta del 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
}
  • Obtener Profundidad

campo del método: profundidad campo de parámetros:

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

Datos que el docker espera en la respuesta del 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]
            // ... 
        ]
    }
}
  • Obtener Trades

campo del método: trades campo de parámetros:

{"symbol":"eth_usdt"}

Datos que el docker espera en la respuesta del 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",
        }
        // ...
    ]
}
  • Obtener registros

campo de método: registros campo de parámetros:

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

Datos que el docker espera en la respuesta del 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],
            // ...
    ]
}
  • Obtener MercadosSe aplicará

campo de método: campo de parámetros:

{}

Datos que el docker espera en la respuesta del protocolo personalizado:

{}
  • Los GetTickersSe aplicará

campo de método: campo de parámetros:

{}

Datos que el docker espera en la respuesta del protocolo personalizado:

{}
  • Obtener Cuenta

campo de método: cuentas campo de parámetros:

{}

Datos que el docker espera en la respuesta del protocolo personalizado:

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

campo del método: activos campo de parámetros:

{}

Datos que el docker espera en la respuesta del protocolo personalizado:

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

campo del método: comercio campo de parámetros:

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

Datos que el docker espera en la respuesta del protocolo personalizado:

{
    "data": {
        "id": "BTC-USDT,123456"
    }
}
  • Obtener órdenes

campo de método: ordenes campo de parámetros:

{"symbol":"ETH_USDT"}

Datos que el docker espera en la respuesta del 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"
        }, 
        // ...
    ]
}
  • Obtener orden

campo de método: orden campo de parámetros:

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

Datos que el docker espera en la respuesta del 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.
    }
}
  • Obtener órdenes de historial

campo de método: historial de pedidos campo de parámetros:

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

Datos que el docker espera en la respuesta del 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 el pedido

campo de método: cancelar campo de parámetros:

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

Datos que el docker espera en la respuesta del protocolo personalizado:

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

Elexchange.IOPor ejemplo, tomemosGET /api/v5/trade/orders-pending, parameters: instType=SPOT, instId=ETH-USDTcomo ejemplo.

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

campo de método:"__api_/api/v5/trade/orders-pending", el campo de método comienza con _Apio, lo que indica que esto se desencadena por elexchange.IOllamada de función en la instancia de la estrategia. campo de parámetros:

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

Datos que el docker espera en la respuesta del 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
}
  • Las demás Otras funciones API de la plataforma FMZ utilizadas en los ejemplos de estrategia, como:exchange.Go(), exchange.GetRawJSON()y otras funciones no necesitan ser encapsuladas, y el método de llamada y la función permanecen sin cambios.

Intercambio de futuros

Además de soportar todas las funciones de los intercambios al contado, los intercambios de futuros también tienen algunas funciones de API que son únicas para los intercambios de futuros.

Se aplicará

  • ObtenerPosiciones
  • Establecer el nivel de margen
  • Obtener fondos

Ejemplo de protocolo personalizado en la versión de Python

Protocolo personalizado REST - Acceso a la interfaz REST API del intercambio OKX, encapsulada como un objeto de intercambio spot. Implementado una interfaz pública de solicitud y respuesta de encapsulación de datos. Implementado una firma de interfaz privada, requisito y respuesta encapsulación de datos. Este ejemplo es principalmente para pruebas y aprendizaje. Las otras interfaces usan datos simulados para responder directamente al docker para 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'
        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.