Guide d'accès au protocole général de la plateforme de trading quantitative Inventor

Créé le: 2024-10-29 14:37:56, Mis à jour le: 2024-11-12 21:58:55
comments   0
hits   241

[TOC]

Guide d’accès au protocole général de la plateforme de trading quantitative Inventor

La plateforme de trading quantitative Inventor prend en charge de nombreux échanges de crypto-monnaie et encapsule les principaux échanges du marché. Cependant, il existe encore de nombreux échanges qui ne sont pas packagés. Pour les utilisateurs qui ont besoin d’utiliser ces échanges, ils peuvent y accéder via le protocole universel quantifié par l’inventeur. Ne se limite pas aux échanges de crypto-monnaies,RESTAccord ouFIXLa plateforme de l’accord est également accessible.

Cet article vaRESTEn prenant l’accès au protocole comme exemple, il explique comment utiliser le protocole général de la plateforme de trading quantitatif Inventor pour encapsuler et accéder à l’API de l’OKX Exchange. Sauf indication contraire, cet article fait référence au protocole général REST.

  • Le déroulement du protocole général est le suivant : Processus de demande : Instance de stratégie exécutée sur le dépositaire -> Programme de protocole général -> API Exchange Processus de réponse : API Exchange -> Programme de protocole général -> Instance de stratégie exécutée sur le dépositaire

1. Configurer l’échange

La page de configuration de l’échange sur la plateforme de trading quantitatif Inventor :

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

Guide d’accès au protocole général de la plateforme de trading quantitative Inventor

  • Sélectionner le protocole : Sélectionnez « Protocole général ».
  • Adresse du service : Le programme de protocole général est essentiellement un service RPC Il est donc nécessaire de spécifier clairement l’adresse de service et le port lors de la configuration de l’échange. Donc dans托管者上运行的策略实例 -> 通用协议程序Pendant le processus, l’hôte sait où accéder au programme de protocole commun. Par exemple:http://127.0.0.1:6666/OKXHabituellement, le programme de protocole commun et l’hôte sont exécutés sur le même périphérique (serveur), donc l’adresse de service est écrite en tant que machine locale (localhost) et le port peut être un port qui n’est pas occupé par le système.
  • Access Key: 托管者上运行的策略实例 -> 通用协议程序Les informations de configuration d’échange transmises pendant le processus.
  • Secret Key: 托管者上运行的策略实例 -> 通用协议程序Les informations de configuration d’échange transmises pendant le processus.
  • Étiquette: L’étiquette d’objet d’échange sur la plateforme de trading quantitative Inventor est utilisée pour identifier un certain objet d’échange.

La capture d’écran de la configuration du plug-in OKX divulguée dans l’article est la suivante :

Guide d’accès au protocole général de la plateforme de trading quantitative Inventor

Informations de configuration de la clé secrète d’échange OKX :

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

2. Déploiement du dépositaire et du programme de protocole universel (plugin)

    1. Hôte Pour exécuter une stratégie en temps réel sur la plateforme de trading quantitatif Inventor, vous devez déployer un dépositaire. Pour le déploiement spécifique du dépositaire, veuillez vous référer au didacticiel de la plateforme, qui ne sera pas répété ici.
    1. Programme de protocole général (plugin) L’hôte et le protocole universel sont généralement déployés sur le même appareil. Le programme de protocole universel (service) peut être écrit dans n’importe quel langage. Cet article est écrit en Python3. Tout comme pour l’exécution de n’importe quel programme Python, vous pouvez l’exécuter directement (effectuez à l’avance diverses configurations de l’environnement Python). Bien entendu, FMZ prend également en charge l’exécution de programmes Python, et ce protocole général peut être exécuté comme un véritable disque pour fournir à la plate-forme de trading quantitative de l’inventeur la prise en charge de l’accès à l’API d’échange non packagée. Une fois le programme du protocole général exécuté, démarrez la surveillance :http://127.0.0.1:6666Dans le programme de protocole général, des chemins spécifiques peuvent être spécifiés, par exemple/OKXà traiter.

3. L’instance de stratégie demande la fonction API FMZ

Lorsque la fonction API de la plateforme (FMZ) est appelée dans la stratégie, le programme de protocole universel reçoit une demande du dépositaire. Vous pouvez également tester en utilisant les outils de débogage de la plateforme, par exemple :

Page des outils de débogage :

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

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

Appelexchange.GetTicker()Fonction, le programme du protocole général reçoit la requête :

POST /OKX HTTP/1.1 
{
    "access_key":"xxx",
    "method":"ticker",
    "nonce":1730275031047002000,
    "params":{"symbol":"LTC_USDT"},
    "secret_key":"xxx"
}
  • access_key : la clé d’échange configurée dans la plateforme « Configurer Exchange » ci-dessus
  • secret_key : la clé d’échange configurée dans la plateforme « Configurer Exchange » ci-dessus
  • méthode : relative à l’interface d’appel dans la stratégie, appelantexchange.GetTicker()heure,methodC’estticker
  • nonce : l’horodatage auquel la demande a eu lieu.
  • params : paramètres liés aux appels d’interface dans la politique.exchange.GetTicker()Lors de l’appel, les paramètres pertinents sont :{"symbol":"LTC_USDT"}

4. Accès du programme de protocole général à l’interface d’échange

Lorsque le programme de protocole général reçoit une demande du dépositaire, il peut obtenir des informations telles que la fonction API de la plateforme (y compris les informations sur les paramètres) demandées par la stratégie, la clé d’échange, etc. en fonction des informations contenues dans la demande.

Sur la base de ces informations, le programme de protocole général peut accéder à l’interface d’échange pour obtenir les données requises ou effectuer certaines opérations.

Habituellement, l’interface d’échange dispose de méthodes telles que GET/POST/PUT/DELETE, qui sont divisées en interface publique et interface privée.

  • Interface publique : une interface qui ne nécessite pas de vérification de signature et qui est directement demandée dans un programme de protocole général.
  • Interface privée : interface qui nécessite une vérification de signature. La signature doit être implémentée dans le programme de protocole général pour demander l’interface API de ces échanges.

Le programme de protocole général reçoit les données de réponse de l’interface d’échange, les traite ultérieurement et les construit dans les données attendues par le dépositaire (décrites ci-dessous). Reportez-vous à OKX spot exchange, implémentation de la classe CustomProtocolOKX dans l’exemple de protocole général PythonGetTickerGetAccountEt d’autres fonctions.

5. Le programme de protocole général répond au dépositaire

Lorsque le programme de protocole général accède à l’interface API de la bourse, effectue certaines opérations ou obtient certaines données, il doit renvoyer les résultats au dépositaire.

Les données remontées au dépositaire varient en fonction de l’interface appelée par la stratégie, et sont d’abord divisées en deux catégories :

  • Le programme de protocole général appelle avec succès l’interface d’échange :
  {
      "data": null,  // "data" can be of any type 
      "raw": null    // "raw" can be of any type 
  }
  • données : La structure spécifique de ce champ est la même que celle de la requête reçue par le programme de protocole général.methodVoici une liste de toutes les interfaces utilisées pour construire la structure de données renvoyée par la fonction API de la plate-forme FMZ.

  • Brut : ce champ peut être utilisé pour transmettre les données brutes de la réponse de l’API Exchange, par exempleexchange.GetTicker()La structure Ticker renvoyée par la fonction contient les informations suivantes enregistrées dans le champ Info de la structure Ticker :rawChamps etdataLes données du champ ; certaines fonctions API de la plateforme n’ont pas besoin de ces données.

  • Le programme de protocole général n’a pas réussi à appeler l’interface d’échange (erreur métier, erreur réseau, etc.)

  {
      "error": ""    // "error" contains an error message as a string
  }
  • erreur : informations d’erreur, qui seront affichées dans le journal des erreurs dans la zone de journal du disque réel de la plate-forme (FMZ), de l’outil de débogage et d’autres pages.

Démontre les données de réponse du protocole général reçues par le programme de politique :

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

Guide d’accès au protocole général de la plateforme de trading quantitative Inventor

6. Accord sur la structure des données dans le protocole général

Ce qui précède est un bref processus de la manière dont le programme de protocole général participe à l’accès à l’API d’échange (FMZ non empaquetée). Ce processus explique uniquement comment appeler l’outil de débogage de la plateforme (FMZ).exchange.GetTicker()Processus de fonctionnement. Ensuite, les détails d’interaction de toutes les fonctions API de la plateforme seront expliqués en détail.

La plateforme encapsule les fonctions communes de plusieurs bourses et les unifie dans une certaine fonction, comme la fonction GetTicker, qui demande les informations actuelles du marché d’un certain produit. Il s’agit essentiellement d’une API dont disposent toutes les bourses. Ainsi, lorsque l’interface API encapsulée de la plateforme est accessible dans l’instance de stratégie, le dépositaire enverra une requête au plug-in « Universal Protocol » (mentionné ci-dessus) :

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

Lors de l’appel de différentes fonctions API encapsulées de la plate-forme d’inventeur dans la stratégie (telles que GetTicker), le format de demande envoyé par le dépositaire au protocole général sera également différent. Les données (JSON) dans le corps ne diffèrent que parmethodetparams. Lors de la conception d’un protocole général,methodVous pouvez effectuer des opérations spécifiques en fonction du contenu. Voici des scénarios de demande-réponse pour toutes les interfaces.

Bourse au comptant

Par exemple, la paire de trading actuelle est :ETH_USDTJe n’entrerai pas dans les détails plus tard. Les données auxquelles le dépositaire s’attend à ce que le protocole général réponde sont principalement écrites dans le champ de données, et un champ brut peut également être ajouté pour enregistrer les données d’origine de l’interface d’échange.

  • GetTicker

    • champ de méthode : « ticker »
    • champ paramètres :
    {"symbol":"ETH_USDT"}
    
    • Données que l’hôte attend dans la réponse générale du protocole :
    {
        "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

    • champ de méthode : « profondeur »
    • champ paramètres :
    {"limit":"30","symbol":"ETH_USDT"}
    
    • Données que l’hôte attend dans la réponse générale du protocole :
    {
        "data" : {
            "time" : 1500793319499,
            "asks" : [
                [1000, 0.5], [1001, 0.23], [1004, 2.1]
                // ... 
            ],
            "bids" : [
                [999, 0.25], [998, 0.8], [995, 1.4]
                // ... 
            ]
        }
    }
    
  • GetTrades

    • champ de méthode : « métiers »
    • champ paramètres :
    {"symbol":"eth_usdt"}
    
    • Données que l’hôte attend dans la réponse générale du protocole :
    { 
        "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

    • champ de méthode : « records »
    • champ paramètres :
    {
        "limit":"500",
        "period":"60",          // 60分钟
        "symbol":"ETH_USDT"
    }
    
    • Données que l’hôte attend dans la réponse générale du protocole :
    {
        "data": [
                // "Time":1500793319000,"Open":1.1,"High":2.2,"Low":3.3,"Close":4.4,"Volume":5.5
                [1500793319, 1.1, 2.2, 3.3, 4.4, 5.5],
                [1500793259, 1.01, 2.02, 3.03, 4.04, 5.05],
                // ...
        ]
    }
    
  • GetMarkets À mettre en œuvre

    • champ de méthode : ””
    • champ paramètres :
    {}
    
    • Données que l’hôte attend dans la réponse générale du protocole :
    {}
    
  • GetTickers À mettre en œuvre

    • champ de méthode : ””
    • champ paramètres :
    {}
    
    • Données que l’hôte attend dans la réponse générale du protocole :
    {}
    
  • GetAccount

    • champ de méthode : « comptes »
    • champ paramètres :
    {}
    
    • Données que l’hôte attend dans la réponse générale du protocole :
    {
        "data": [
            {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
            {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
            // ...
        ]
    }
    
  • GetAssets

    • champ de méthode : « assets »
    • champ paramètres :
    {}
    
    • Données que l’hôte attend dans la réponse générale du protocole :
    {
        "data": [
            {"currency": "TUSD", "free": "3000", "frozen": "0"},
            {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
            // ...
        ]
    }
    
  • CreateOrder / Buy / Sell

    • champ de méthode : « commerce »
    • champ paramètres :
    {"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
    
    • Données que l’hôte attend dans la réponse générale du protocole :
    {
        "data": {
            "id": "BTC-USDT,123456"
        }
    }
    
  • GetOrders

    • champ de méthode : « commandes »
    • champ paramètres :
    {"symbol":"ETH_USDT"}
    
    • Données que l’hôte attend dans la réponse générale du protocole :
    {
        "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

    • champ de méthode : « commande »
    • champ paramètres :
    {
        "id":"ETH-USDT,123456",       // 策略中调用:exchange.GetOrder("ETH-USDT,123456")
        "symbol":"ETH_USDT"
    }
    
    • Données que l’hôte attend dans la réponse générale du protocole :
    { 
        "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
        }
    }
    
  • GetHistoryOrders

    • champ de méthode : « historyorders »
    • champ paramètres :
    {"limit":0,"since":0,"symbol":"ETH_USDT"}
    
    • Données que l’hôte attend dans la réponse générale du protocole :
    {
        "data": [
            {
                "id": "ETH-USDT,123456",
                "symbol": "ETH_USDT",
                "amount": 0.25,
                "price": 1005,
                "deal_amount": 0,
                "avg_price": 1000,
                "type": "buy",       // "buy"、"sell"
                "status": "filled",  // "filled"
            }, 
            // ...
        ]
    }
    
  • CancelOrder

    • champ de méthode : « annuler »
    • champ paramètres :
    {"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
    
    • Données que l’hôte attend dans la réponse générale du protocole :
    {
        "data": true    // 只要该JSON中没有error字段,都默认为撤单成功
    }
    
  • IO

La fonction exchange.IO permet d’accéder directement à l’interface d’échange. Par exemple, nous utilisonsGET /api/v5/trade/orders-pending, 参数:instType=SPOT,instId=ETH-USDTPar exemple.

  // 策略实例中调用
  exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")
  • champ de méthode :"__api_/api/v5/trade/orders-pending"Le champ de méthode commence par _api, indiquant que cela est déclenché par l’appel de fonction exchange.IO dans l’instance de stratégie.

  • champ paramètres :

    {"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
    
  • Données que l’hôte attend dans la réponse générale du protocole :

    {
        "data": {"code": "0", "data": [], "msg": ""}    // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
    }
    
  • autre Autres fonctions API de la plateforme Inventor utilisées dans les exemples de stratégie, telles que : exchange.Go()exchange.GetRawJSON()Des fonctions comme celles-ci n’ont pas besoin d’être encapsulées, et la méthode d’appel et les fonctionnalités restent inchangées.

Bourse à terme

En plus de prendre en charge toutes les fonctions des bourses au comptant, les bourses à terme disposent également de certaines fonctions API qui leur sont propres.

À mettre en œuvre

  • GetPositions
  • SetMarginLevel
  • GetFundings

Version Python de l’exemple de protocole général

Protocole général REST - accès à l’interface API REST de l’échange OKX et l’encapsulation en tant qu’objet d’échange spot. Implémentation d’une interface commune pour l’encapsulation des données de demande et de réponse. Implémentation d’une signature d’interface privée, d’une encapsulation des données de demande et de réponse. Cet exemple est principalement destiné aux tests et à l’apprentissage. Les autres interfaces utilisent des données simulées pour répondre directement à l’hôte à des fins de test.

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