Die Ressourcen sind geladen. Beförderung...

Inventor Quantitative Trading Plattform General Protocol Zugriffsanleitung

Schriftsteller:Die Erfinder quantifizieren - Kleine Träume, Erstellt: 2024-10-29 14:37:56, Aktualisiert: 2024-11-12 21:58:55

[TOC]

img

Die Erfinder-Quantitäts-Trading-Plattform unterstützt eine Vielzahl von Kryptowährungs-Exchanges und umfasst die hauptsächlich auf dem Markt befindlichen Exchanges. Es gibt jedoch noch viele unumfassende Exchanges, die für Benutzer zugänglich sind, die diese benötigen, und die über die Erfinder-Quantitäts-Generalprotokolle zugänglich sind.RESTAbkommen oderFIXDie Plattformen des Protokolls sind ebenfalls zugänglich.

Ich werdeRESTAls Beispiel für Protokollzugang wird beschrieben, wie man das allgemeine Protokoll der Erfinder-Quantitative-Handelsplattform verwendet, um die OKX-Börsen-API zu verpacken und darauf zuzugreifen.

  • Die Arbeitsprozesse für die Allgemeine Vereinbarung sind: Anforderungsprozess: Instanz von Strategien, die auf dem Treuhänder ausgeführt werden -> General Protocol Procedure -> Exchange API Beantwortungsprozess: Exchange API -> General Protocol Procedure -> Instanzen von Strategien, die auf dem Treuhänder ausgeführt werden

1. Konfigurierung der Börse

Die Seite, auf der die Erfinder die Quantitative Handelsplattform konfigurieren:

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

img

  • Die Wahl des Protokolls: Wählen Sie das "Allgemeine Protokoll".
  • Service-Adresse: Der General Protocol-Prozess ist im Wesentlichen ein RPC-Dienst Es ist daher notwendig, bei der Konfiguration der Börse die Dienstadresse, den Port zu spezifizieren.托管者上运行的策略实例 -> 通用协议程序Während des Prozesses wissen die Administratoren, wo sie auf das Protokoll zugreifen sollen. Zum Beispiel:http://127.0.0.1:6666/OKXNormalerweise laufen die UTP-Programme mit dem Host auf demselben Gerät (Server), so dass die Dienstadresse auf dem lokalen Host geschrieben wird und der Port für ein System verwendet wird, das nicht besetzt ist.
  • Zugriffsschlüssel:托管者上运行的策略实例 -> 通用协议程序Während des Prozesses werden die Angabeinformationen der übertragenen Börsen konfiguriert.
  • Geheimer Schlüssel:托管者上运行的策略实例 -> 通用协议程序Während des Prozesses werden die Angabeinformationen der übertragenen Börsen konfiguriert.
  • Sie ist ein junger Mann. Ein Gegenstands-Label, das auf einer quantitativen Handelsplattform des Erfinders verwendet wird, um ein Gegenstand an einer Börse zu kennzeichnen.

Die Konfiguration des OKX-Plugins, die in diesem Artikel veröffentlicht wurde, zeigt sich wie folgt:

img

OKX-Börse: Geheimschlüssel-Konfiguration:

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

2. Implementieren von Administratoren und Plugins

  • 1. Verwalter Jede Strategie, die auf einer inventor quantified trading platform ausgeführt wird, muss einen Trustee bereitstellen. Der Trustee kann sich auf die Plattform-Tutorials beziehen.

  • 2, Allgemeines Protokoll Programm (plugin) Der Host und das General Protocol werden in der Regel auf demselben Gerät bereitgestellt. Die General Protocol-Programme (Dienstleistungen) können in jeder Sprache geschrieben werden. Natürlich wird auch auf FMZ Python-Programme unterstützt, aber man kann auch das GNU-Protokoll als Festplatte ausführen, um den Erfindern eine Quantitative-Trading-Plattform für den Zugang zu unverpackten Exchange-APIs zu bieten. Nach der Einführung des allgemeinen Protokolls begann man zu zuhören:http://127.0.0.1:6666In einem Protokoll kann man einen spezifischen Pfad verwenden, z.B./OKXIch habe mich nicht geäußert.

3. Ein Beispiel für die Anfrage einer API-Funktion für FMZ

Wenn die API-Funktion der FMZ-Plattform in der Strategie aufgerufen wird, erhält der UTP-Prozessor eine Anfrage vom Host.

Debugger-Tool-Seite:

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

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

Anrufeexchange.GetTicker()Funktion, General-Protokoll-Prozessor erhält Anfrage:

POST /OKX HTTP/1.1 
{
    "access_key":"xxx",
    "method":"ticker",
    "nonce":1730275031047002000,
    "params":{"symbol":"LTC_USDT"},
    "secret_key":"xxx"
}
  • access_key: Der geheime Schlüssel der Börse, der in der Plattform "Börse konfigurieren" aufgeführt ist
  • secret_key: Der geheime Schlüssel der Börse, der im obigen Artikel beim "Konfigurieren der Börse" der Plattform verwendet wird
  • Methode: bezieht sich auf die Interface, die in der Strategie aufgerufen wirdexchange.GetTicker()Ich bin nicht derjenige.methodDas istticker
  • Nonce: Zeit, in der die Anfrage eingegangen ist.
  • Params: Parameter, die bei einem Interface-Aufruf in der Politik relevant sind, in diesem Fallexchange.GetTicker()Bei Anrufen werden folgende Parameter verwendet:{"symbol":"LTC_USDT"}

4. Ein allgemeiner Protokollprozess für den Zugriff auf die Börsenoberfläche

Wenn ein Protokollprogramm eine Anfrage von einem Treuhänder empfängt, kann es Informationen über die in der Anfrage enthaltene Information erfahren: Informationen über die angeforderte Plattform-API-Funktion (einschließlich Parameter-Informationen) oder den geheimen Schlüssel der Börse.

Auf der Grundlage dieser Informationen kann ein General-Protokoll-Prozessor auf die Interface der Börse zugreifen, Daten abrufen oder bestimmte Aktionen ausführen.

Normalerweise gibt es an den Interfaces der Börsen Methoden wie GET / POST / PUT / DELETE, die in öffentliche und private Interfaces unterteilt sind.

  • Öffentliche Schnittstelle: Eine Schnittstelle ohne Unterschrift-Verifizierung, direkt in der allgemeinen Protokoll-Prozedur angefordert.
  • Private Schnittstellen: Schnittstellen, die eine Signatur-Verifizierung erfordern, die eine Signatur in einem allgemeinen Protokollprozess ermöglichen, um dann die API-Schnittstellen dieser Börsen anzufordern.

Die General Protocol-Prozedur empfängt die Antwortdaten der Exchange-Interface, die weiter verarbeitet werden und die Daten bilden, die von den Treuhändern erwartet werden (siehe unten). Verwenden Sie OKX-Kontobörse, eine Implementierung der OKX-Klasse CustomProtocol im Python-Generalprotokoll-ParameterGetTickerGetAccountDie Gleichgewichtsfunktion.

5. Die Daten werden von den Protokollprozessoren an die Administratoren weitergeleitet

Wenn ein Protokollprogramm die API-Schnittstelle einer Börse aufruft, bestimmte Vorgänge ausführt oder bestimmte Daten erhält, muss es das Ergebnis an den Treuhänder zurückgeben.

Die Daten, die an den Administrator zurückgegeben werden, unterscheiden sich je nach Interface, auf die die Strategie aufgerufen wird, und werden zunächst in zwei Kategorien eingeteilt:

  • Die Exchange-Schnittstelle wurde erfolgreich durch den General Protocol-Prozess aufgerufen:

    {
        "data": null,  // "data" can be of any type 
        "raw": null    // "raw" can be of any type 
    }
    
    • data: Die spezifische Struktur des Felds und die Anfragen, die der General-Protokoll-Prozess erhältmethodDie Datenstrukturen, die für die Konstruktion der FMZ-Plattform-API-Funktion verwendet werden, werden im Folgenden aufgelistet.
    • raw: Dieses Feld kann die Rohdaten der API-Antworten der Börse übertragen, z. B.exchange.GetTicker()Die Funktion, die die Tickeraufbauung zurückgibt, wird in dem Info-Feld der Tickeraufbauung erfasst, das heißt:rawFeld unddataDie Daten der Felder; die API-Funktionen der Plattform, einige benötigen diese Daten nicht.
  • Fehler beim Aufruf der Exchange-Interface durch den General Protocol-Prozess (Geschäftsfehler, Netzwerkfehler usw.)

    {
        "error": ""    // "error" contains an error message as a string
    }
    
    • error: Fehlermeldung, die in den Fehlerlogs von Seitenlog-Bereichen wie der FMZ-Plattform, Debugging-Tools und anderen angezeigt wird.

Das Programm zeigt die Antwortdaten, die das General Protocol (GPR) erhalten hat:

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

img

6. Datenstrukturvereinbarungen im Protokoll

Das ist ein kurzer Prozess, in dem die General Protocol-Programmer an der Zugangs- (FMZ unwrapped) Austausch-API teilnehmen. Dieser Prozess beschreibt nur Anrufe in den Debugging-Tools der FMZ-Plattform.exchange.GetTicker()Der Prozess bei der Funktion. Die Interaktionsdetails aller Plattform-API-Funktionen werden weiter unten beschrieben.

Die Plattform verpackt die gemeinsamen Funktionen der verschiedenen Börsen und vereinheitlicht sie in eine Funktion, z. B. die GetTicker-Funktion, die aktuelle Marktinformationen einer Art anfordert, die im Wesentlichen von allen Börsen verfügbar ist.

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

Bei Anrufen von API-Funktionen (z. B. GetTicker), die von verschiedenen Erfinderplattformen verpackt sind, unterscheidet sich auch die Form der Anfragen, die von den Hosts an das UTP gesendet werden.methodundparams◦ Die Entwicklung des allgemeinen Protokolls basiert auf den folgenden Grundsätzen:methodDer Inhalt des Interfaces ist für die Ausführung bestimmter Operationen geeignet.

Börsen

Das ist ein Beispiel für ein aktuelles Handelspaar:ETH_USDTDie Daten, auf die die Treuhänder eine Antwort des UTP erwarten, werden hauptsächlich in einem Data-Feld geschrieben.

  • GetTicker

    • Methode-Feld: Ticker-Feld
    • Params-Felder:
      {"symbol":"ETH_USDT"}
      
    • Die Treuhänder erwarten, dass die Daten, auf die das Protokoll antwortet:
      {
          "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

    • Methode-Feld: depth
    • Params-Felder:
      {"limit":"30","symbol":"ETH_USDT"}
      
    • Die Treuhänder erwarten, dass die Daten, auf die das Protokoll antwortet:
      {
          "data" : {
              "time" : 1500793319499,
              "asks" : [
                  [1000, 0.5], [1001, 0.23], [1004, 2.1]
                  // ... 
              ],
              "bids" : [
                  [999, 0.25], [998, 0.8], [995, 1.4]
                  // ... 
              ]
          }
      }
      
  • GetTrades

    • Methode-Feld: trades
    • Params-Felder:
      {"symbol":"eth_usdt"}
      
    • Die Treuhänder erwarten, dass die Daten, auf die das Protokoll antwortet:
      { 
          "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

    • Methode-Feld: Methode-Feld: Methode-Feld
    • Params-Felder:
      {
          "limit":"500",
          "period":"60",          // 60分钟
          "symbol":"ETH_USDT"
      }
      
    • Die Treuhänder erwarten, dass die Daten, auf die das Protokoll antwortet:
      {
          "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],
                  // ...
          ]
      }
      
  • GetMarketsWirklichkeit

    • Methode-Feld:""
    • Params-Felder:
      {}
      
    • Die Treuhänder erwarten, dass die Daten, auf die das Protokoll antwortet:
      {}
      
  • GetTickersWirklichkeit

    • Methode-Feld:""
    • Params-Felder:
      {}
      
    • Die Treuhänder erwarten, dass die Daten, auf die das Protokoll antwortet:
      {}
      
  • GetAccount

    • Methode-Feld: accounts
    • Params-Felder:
      {}
      
    • Die Treuhänder erwarten, dass die Daten, auf die das Protokoll antwortet:
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • GetAssets

    • Methode-Feld: Vermögenswerte
    • Params-Felder:
      {}
      
    • Die Treuhänder erwarten, dass die Daten, auf die das Protokoll antwortet:
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"},
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • CreateOrder / Kauf / Verkauf

    • Methode-Feld: Methode für den Handel
    • Params-Felder:
      {"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
      
    • Die Treuhänder erwarten, dass die Daten, auf die das Protokoll antwortet:
      {
          "data": {
              "id": "BTC-USDT,123456"
          }
      }
      
  • GetOrders

    • Methode-Feld: orders
    • Params-Felder:
      {"symbol":"ETH_USDT"}
      
    • Die Treuhänder erwarten, dass die Daten, auf die das Protokoll antwortet:
      {
          "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

    • Methode-Feld: order
    • Params-Felder:
      {
          "id":"ETH-USDT,123456",       // 策略中调用:exchange.GetOrder("ETH-USDT,123456")
          "symbol":"ETH_USDT"
      }
      
    • Die Treuhänder erwarten, dass die Daten, auf die das Protokoll antwortet:
      { 
          "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
          }
      }
      
  • GetHistory-Orders

    • Methode-Feld: Historienaufträge
    • Params-Felder:
      {"limit":0,"since":0,"symbol":"ETH_USDT"}
      
    • Die Treuhänder erwarten, dass die Daten, auf die das Protokoll antwortet:
      {
          "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"
              }, 
              // ...
          ]
      }
      
  • Aufforderung stornieren

    • Methode-Feld: Abbrechen
    • Params-Felder:
      {"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
      
    • Die Treuhänder erwarten, dass die Daten, auf die das Protokoll antwortet:
      {
          "data": true    // 只要该JSON中没有error字段,都默认为撤单成功
      }
      
  • Zwischenzeit

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

    // 策略实例中调用
    exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")
    
    • Methode-Feld:"__api_/api/v5/trade/orders-pending"Der Inhalt des Methode-Feldes beginnt mit _api_, was bedeutet, dass dies durch den Aufruf der Exchange.IO-Funktion in der Politikinstanz ausgelöst wird.
    • Params-Felder:
      {"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
      
    • Die Treuhänder erwarten, dass die Daten, auf die das Protokoll antwortet:
      {
          "data": {"code": "0", "data": [], "msg": ""}    // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
      }
      
  • Weitere Weitere API-Funktionen der Erfinderplattform, die in den Strategien verwendet werden, wie z. B.:exchange.Go()exchange.GetRawJSON()Die Funktion ist unveränderlich, sie muss nicht umhüllt werden.

Futures-Börsen

Zusätzlich zu den Funktionen, die Futures-Börsen für alle Depot-Börsen unterstützen, gibt es auch einige Futures-Börsen-spezifische API-Funktionen.

Wirklichkeit

  • GetPositions
  • Margin-Level festlegen
  • GetFundings

Python-Versions-Generalprotokoll-Beispiele

Die REST-Generalschaltfläche wird in die OKX-Börse REST-API-Schnittstelle eingegliedert und als Objekt für die Börse verpackt. Die Implementierung einer öffentlichen Anfrage-Antwort-Interface umfasst Daten. Ein privates Signatur-, Anforderungs- und Antwortdaten-Wund umsetzen. Dieses Modell ist hauptsächlich für das Testlernen ausgelegt, während die restlichen Anwendungen die simulierten Daten direkt an die Administratoren senden, um sie zu testen.

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

Mehr