Les ressources ont été chargées... Je charge...

Guide d'accès au protocole général pour les plateformes de négociation quantitative pour les inventeurs

Auteur:L'inventeur de la quantification - un petit rêve, Créé: 2024-10-29 14:37:56, Mis à jour: 2024-11-12 21:58:55

[TOC] Je vous en prie.

img

Les plateformes de trading quantifiées par les inventeurs prennent en charge de nombreux échanges de crypto-monnaie et sont enveloppées dans les échanges traditionnels sur le marché. Cependant, de nombreux échanges restent non enveloppés, et les utilisateurs qui ont besoin d'utiliser ces échanges peuvent y accéder via des protocoles généraux quantifiés par les inventeurs.RESTaccord ouLe nombre de personnesLes plateformes du protocole sont également accessibles.

Cet article est intituléRESTL'exemple de l'accès au protocole, expliquant comment utiliser le protocole général de la plate-forme de négociation quantitative des inventeurs pour envelopper et accéder à l'API de l'échange OKX.

  • Le processus de travail du protocole général est le suivant: Processus de demande: Exemple de stratégie en cours d'exécution sur le gestionnaire -> Procédure de protocole général -> API de l'échange Processus de réponse: API de l'échange -> Procédure de protocole général -> Exemple de stratégie en cours d'exécution sur le gestionnaire

1° Configurer les échanges

La page de configuration des plateformes de négociation quantitative par les inventeurs:

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

img

  • Sélectionnez le protocole: Sélectionnez le protocole général.
  • Adresse du service: Le protocole général est essentiellement un service RPC Il est donc nécessaire de spécifier clairement l'adresse du service, le port lors de la configuration de l'échange.托管者上运行的策略实例 -> 通用协议程序Les administrateurs ne savent où accéder au protocole général que pendant le processus. Par exemple:http://127.0.0.1:6666/OKXLe protocole général est généralement exécuté sur le même appareil (serveur) que l'hôte, de sorte que l'adresse du service est écrite sur le localhost et que le port est utilisé par un système non occupé.
  • Clé d' accès:托管者上运行的策略实例 -> 通用协议程序Au cours du processus, les informations de configuration de l'échange sont transmises.
  • Clé secrète:托管者上运行的策略实例 -> 通用协议程序Au cours du processus, les informations de configuration de l'échange sont transmises.
  • Le blogueur a écrit: Étiquette d'objet d'un échange sur une plateforme de négociation quantitative de l'inventeur, utilisée pour identifier un objet d'échange.

Voici une capture d'écran de la configuration du plugin OKX publié dans l'article:

img

Les informations de configuration de la clé secrète de l'échange OKX:

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

2, déployer des hôtes et des plugins

  • 1er, le gestionnaire Pour exécuter n'importe quelle stratégie sur une plateforme de trading quantifié par l'inventeur, il est nécessaire de déployer un gestionnaire, qui peut être consulté dans les didacticiels de la plateforme.

  • 2, le protocole général (plugin) Les hôtes et le protocole général sont généralement déployés sur le même appareil. Les programmes de protocole général (services) peuvent être écrits dans n'importe quel langage pour la conception. Bien sûr, il est également possible d'exécuter des programmes python sur FMZ, ou d'exécuter ce protocole général comme un disque dur pour fournir aux inventeurs une plate-forme de négociation quantitative pour l'accès à l'API de l'échange non enveloppé. Le protocole général est en cours d'exécution et les écouteurs commencent à écouter:http://127.0.0.1:6666Le protocole générique permet de tracer des chemins spécifiques, par exemple:/OKXIl y a des gens qui sont en train d'écrire.

3, l'instance de stratégie demande une fonction API pour FMZ

Lorsqu'une fonction API de la plate-forme FMZ est appelée dans la stratégie, le protocole général reçoit une demande de l'hôte. Des outils de débogage de la plate-forme peuvent également être testés, par exemple:

Le site de l'outil de débogage:

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

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

Appeléeexchange.GetTicker()Les fonctions, les protocoles génériques ont reçu la demande:

POST /OKX HTTP/1.1 
{
    "access_key":"xxx",
    "method":"ticker",
    "nonce":1730275031047002000,
    "params":{"symbol":"LTC_USDT"},
    "secret_key":"xxx"
}
  • access_key: clé secrète de l'échange configurée lors de la "configuration de l'échange" de la plate-forme dans le texte ci-dessus
  • secret_key: clé secrète de l'échange configurée lors de la "configuration de l'échange" de la plate-forme dans l'article ci-dessus
  • méthode: liée à l'interface appelée dans la stratégie, appeléeexchange.GetTicker()Je ne sais pas.methodC'est à direticker
  • Nonce: le moment où la demande a été faite.
  • Paramètres: paramètres liés à l'appel d'interface dans la politique, dans ce casexchange.GetTicker()Les paramètres sont les suivants:{"symbol":"LTC_USDT"}

4, les procédures de protocole général pour accéder à l'interface de l'échange

Lorsque le protocole général reçoit une demande de l'administrateur, il peut être informé de l'information contenue dans la demande: fonction API de la plate-forme demandée (y compris les paramètres), clé secrète de l'échange, etc.

Sur la base de cette information, le protocole général peut accéder à l'interface de l'échange, obtenir les données requises ou effectuer certaines opérations.

Les interfaces d'échange ont généralement des méthodes telles que GET / POST / PUT / DELETE, qui sont divisées en interfaces publiques et privées.

  • Interface publique: Interface qui ne nécessite pas d'authentification de signature, directement demandée dans le protocole général.
  • Interfaces privées: interfaces qui nécessitent une authentification de la signature, qui nécessitent une signature dans un processus de protocole général et qui demandent ensuite une interface API à ces échanges.

Le protocole général reçoit les données de réponse de l'interface de l'échange, qui sont ensuite traitées pour constituer les données attendues par le dépositaire (voir ci-dessous). En référence à OKX Exchange, dans le cas de Python, le protocole personnalisé est une implémentation de classe OKXGetTickerGetAccountLes fonctions égales.

5. Les protocoles génériques répondent aux administrateurs

Lorsqu'un protocole général accède à l'API d'un échange, exécute certaines opérations ou obtient certaines données, il doit retourner les résultats à l'administrateur.

Les données fournies aux hôtes diffèrent en fonction de l'interface utilisée pour l'appel de la stratégie.

  • Le protocole général a appelé l'interface de l'échange avec succès:

    {
        "data": null,  // "data" can be of any type 
        "raw": null    // "raw" can be of any type 
    }
    
    • data: la structure spécifique du champ et les demandes reçues par le protocole généralmethodLes structures de données utilisées pour construire les fonctions d'API de la plateforme FMZ sont répertoriées ci-dessous.
    • raw: ce champ peut transmettre des données brutes de la réponse de l'API de l'échange, par exempleexchange.GetTicker()La structure de ticker retournée par la fonction, enregistrée dans le champ Info de la structure de ticker estrawchamps etdataLes données des champs; les fonctions d'API de la plateforme, certaines ne nécessitent pas ces données.
  • Échec de l'interface d'appel du protocole général (erreur opérationnelle, erreur réseau, etc.)

    {
        "error": ""    // "error" contains an error message as a string
    }
    
    • error: message d'erreur, qui s'affiche dans le journal d'erreur des zones de journal de page telles que le disque dur de la plateforme FMZ, les outils de débogage.

Les données de réponse du protocole général reçues par le programme de stratégie de démonstration:

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

img

Conventions sur la structure des données dans le protocole général

Ce qui précède est un bref processus de participation des protocoles généraux à l'API d'un échange d'accès (FMZ non enveloppé), qui n'explique que les appels dans les outils de débogage de la plate-forme FMZ.exchange.GetTicker()Le processus au moment de la fonction. Les détails d'interaction de toutes les fonctions API de plate-forme sont détaillés ci-dessous.

La plate-forme enveloppe les fonctionnalités communes des différents échanges, qui sont unifiées en une fonction, comme la fonction GetTicker, qui demande des informations de marché d'une certaine variété actuelle, qui est essentiellement l'API disponible pour toutes les échanges. Ainsi, lorsque, dans l'exemple de stratégie, l'administrateur accède à l'interface API enveloppée par la plate-forme, il envoie une demande au plugin "General Protocol" (mentionné ci-dessus):

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

Lorsque des fonctions d'API encapsulées par différentes plateformes d'inventeurs sont appelées dans la stratégie (par exemple, GetTicker), le format de demande envoyé par l'hôte au protocole général diffère également.methodetparamsLe protocole générique doit être conçu en fonction des principes suivants:methodLe contenu de l'interface peut être utilisé pour exécuter des opérations spécifiques.

Le marché au comptant

Par exemple, la paire de transactions en cours est:ETH_USDTLes données auxquelles les administrateurs s'attendent à ce que le protocole général réponde sont principalement écrites dans le champ de données, mais un champ RAW peut également être ajouté pour enregistrer les données originales de l'interface d'échange.

  • GetTicker

    • Le champ méthode:
    • Le champ params:
      {"symbol":"ETH_USDT"}
      
    • Les gestionnaires s'attendent à ce que le protocole général réponde à:
      {
          "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接口应答的原始数据
      }
      
  • Obtenez la profondeur

    • Le champ méthode: depth
    • Le champ params:
      {"limit":"30","symbol":"ETH_USDT"}
      
    • Les gestionnaires s'attendent à ce que le protocole général réponde à:
      {
          "data" : {
              "time" : 1500793319499,
              "asks" : [
                  [1000, 0.5], [1001, 0.23], [1004, 2.1]
                  // ... 
              ],
              "bids" : [
                  [999, 0.25], [998, 0.8], [995, 1.4]
                  // ... 
              ]
          }
      }
      
  • GetTrades

    • Le champ méthode:
    • Le champ params:
      {"symbol":"eth_usdt"}
      
    • Les gestionnaires s'attendent à ce que le protocole général réponde à:
      { 
          "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",
              }
              // ...
          ]
      }
      
  • Obtenez des enregistrements

    • Le champ méthode:
    • Le champ params:
      {
          "limit":"500",
          "period":"60",          // 60分钟
          "symbol":"ETH_USDT"
      }
      
    • Les gestionnaires s'attendent à ce que le protocole général réponde à:
      {
          "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À réaliser

    • le champ méthode:""
    • Le champ params:
      {}
      
    • Les gestionnaires s'attendent à ce que le protocole général réponde à:
      {}
      
  • - Je ne sais pas.À réaliser

    • le champ méthode:""
    • Le champ params:
      {}
      
    • Les gestionnaires s'attendent à ce que le protocole général réponde à:
      {}
      
  • Compte de prise de vue

    • Le champ méthode:
    • Le champ params:
      {}
      
    • Les gestionnaires s'attendent à ce que le protocole général réponde à:
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • GetAssets

    • Le champ méthode:
    • Le champ params:
      {}
      
    • Les gestionnaires s'attendent à ce que le protocole général réponde à:
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"},
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • Créer ordre / acheter / vendre

    • Le champ méthode:
    • Le champ params:
      {"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
      
    • Les gestionnaires s'attendent à ce que le protocole général réponde à:
      {
          "data": {
              "id": "BTC-USDT,123456"
          }
      }
      
  • Obtenir des ordres

    • champs de méthode: orders
    • Le champ params:
      {"symbol":"ETH_USDT"}
      
    • Les gestionnaires s'attendent à ce que le protocole général réponde à:
      {
          "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"
              }, 
              // ...
          ]
      }
      
  • Obtenir un ordre

    • Le champ méthode:
    • Le champ params:
      {
          "id":"ETH-USDT,123456",       // 策略中调用:exchange.GetOrder("ETH-USDT,123456")
          "symbol":"ETH_USDT"
      }
      
    • Les gestionnaires s'attendent à ce que le protocole général réponde à:
      { 
          "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
          }
      }
      
  • Les commandes GetHistory

    • champs de méthode:
    • Le champ params:
      {"limit":0,"since":0,"symbol":"ETH_USDT"}
      
    • Les gestionnaires s'attendent à ce que le protocole général réponde à:
      {
          "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"
              }, 
              // ...
          ]
      }
      
  • Annuler la commande

    • Le champ méthode:
    • Le champ params:
      {"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
      
    • Les gestionnaires s'attendent à ce que le protocole général réponde à:
      {
          "data": true    // 只要该JSON中没有error字段,都默认为撤单成功
      }
      
  • - Je vous en prie

    exchange.IO函数用于直接访问交易所接口,例如我们以GET /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")
    
    • Le champ méthode:"__api_/api/v5/trade/orders-pending"Le contenu du champ méthode commence par _api_, ce qui indique qu'il est déclenché par l'appel de la fonction exchange.IO dans l'exemple de stratégie.
    • Le champ params:
      {"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
      
    • Les gestionnaires s'attendent à ce que le protocole général réponde à:
      {
          "data": {"code": "0", "data": [], "msg": ""}    // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
      }
      
  • Autres D'autres fonctions d'API de plate-forme d'inventeur utilisées dans les exemples de stratégie, par exemple:exchange.Go()exchange.GetRawJSON()Les fonctions, par exemple, n'ont pas besoin d'être enveloppées, la façon dont elles sont appelées, les fonctions ne changent pas.

Bourse à terme

En plus des fonctions prises en charge par tous les échanges sur place, il existe des fonctions API spécifiques aux échanges à terme.

À réaliser

  • Obtenez des positions
  • Définir le niveau de marge
  • GetFundings

Exemple de protocole général pour la version Python

Le protocole général REST se connecte à l'interface REST API de l'échange OKX et est enveloppé en objet d'échange instantané. L'enveloppe de données de demande et de réponse est réalisée à partir d'une interface publique. Une signature, une requête et une réponse enveloppent les données d'une interface privée. Ce modèle est principalement destiné à l'apprentissage par test, le reste de l'interface utilise des données d'analyses pour répondre directement aux hôtes pour les tests.

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

Plus de