avatar of 发明者量化-小小梦 发明者量化-小小梦
konzentrieren Sie sich auf Private Nachricht
4
konzentrieren Sie sich auf
1271
Anhänger

Inventor Quantitative Trading Platform – Allgemeiner Protokollzugriffsleitfaden

Erstellt in: 2024-10-29 14:37:56, aktualisiert am: 2024-11-12 21:58:55
comments   0
hits   865

[TOC]

Inventor Quantitative Trading Platform – Allgemeiner Protokollzugriffsleitfaden

Die Inventor Quantitative Trading Platform unterstützt viele Kryptowährungsbörsen und umfasst die wichtigsten Börsen auf dem Markt. Es gibt jedoch immer noch viele Börsen, die nicht verpackt sind. Benutzer, die diese Börsen verwenden müssen, können über das vom Erfinder quantifizierte universelle Protokoll darauf zugreifen. Nicht beschränkt auf Kryptowährungsbörsen, jedeRESTVereinbarung oderFIXDarüber hinaus ist auch die Plattform der Vereinbarung abrufbar.

Dieser Artikel wirdRESTAm Beispiel des Protokollzugriffs wird erklärt, wie das allgemeine Protokoll der Inventor Quantitative Trading Platform verwendet wird, um die API der OKX Exchange zu kapseln und darauf zuzugreifen. Sofern nicht anders angegeben, bezieht sich dieser Artikel auf das allgemeine REST-Protokoll.

  • Der Arbeitsablauf des allgemeinen Protokolls ist: Anforderungsprozess: Strategieinstanz wird auf dem Depotverwalter ausgeführt -> Allgemeines Protokollprogramm -> Exchange-API Antwortprozess: Exchange API -> Allgemeines Protokollprogramm -> Strategieinstanz, die auf dem Depotverwalter ausgeführt wird

1. Konfigurieren Sie den Austausch

Die Seite zur Konfiguration des Austauschs auf der Inventor Quantitative Trading Platform:

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

Inventor Quantitative Trading Platform – Allgemeiner Protokollzugriffsleitfaden

  • Protokoll auswählen: „Allgemeines Protokoll“ auswählen.
  • Serviceadresse: Das allgemeine Protokollprogramm ist im Wesentlichen ein RPC-Dienst Daher ist es notwendig, bei der Konfiguration der Vermittlungsstelle die Serviceadresse und den Port eindeutig anzugeben. Also in托管者上运行的策略实例 -> 通用协议程序Während des Vorgangs weiß der Host, wo er auf das gemeinsame Protokollprogramm zugreifen kann. Zum Beispiel:http://127.0.0.1:6666/OKXNormalerweise werden das gemeinsame Protokollprogramm und der Host auf demselben Gerät (Server) ausgeführt, sodass die Dienstadresse als lokale Maschine (localhost) geschrieben wird und der Port ein Port sein kann, der nicht vom System belegt ist.
  • Access Key: 托管者上运行的策略实例 -> 通用协议程序Die während des Vorgangs übermittelten Exchange-Konfigurationsinformationen.
  • Secret Key: 托管者上运行的策略实例 -> 通用协议程序Die während des Vorgangs übermittelten Exchange-Konfigurationsinformationen.
  • Etikett: Die Tauschobjektbezeichnung auf der Inventor Quantitative Trading Platform dient der Identifizierung eines bestimmten Tauschobjekts.

Der im Artikel veröffentlichte Screenshot der OKX-Plugin-Konfiguration sieht wie folgt aus:

Inventor Quantitative Trading Platform – Allgemeiner Protokollzugriffsleitfaden

Informationen zur Konfiguration des geheimen OKX-Austauschschlüssels:

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

2. Bereitstellung des Custodian und des universellen Protokollprogramms (Plugin)

    1. Gastgeber Um eine Echtzeitstrategie auf der Inventor Quantitative Trading Platform auszuführen, müssen Sie einen Custodian einsetzen. Informationen zur spezifischen Bereitstellung des Custodian finden Sie im Plattform-Tutorial, das hier nicht wiederholt wird.
    1. Allgemeines Protokollprogramm (Plugin) Der Host und das Universalprotokoll werden normalerweise auf demselben Gerät bereitgestellt. Das Universalprotokoll-Programm (Dienstprogramm) kann in jeder beliebigen Sprache geschrieben werden. Dieser Artikel wurde in Python3 geschrieben. Sie können es wie jedes andere Python-Programm direkt ausführen (nehmen Sie dazu vorab verschiedene Konfigurationen der Python-Umgebung vor). Natürlich unterstützt FMZ auch die Ausführung von Python-Programmen, und dieses allgemeine Protokoll kann als echte Festplatte ausgeführt werden, um der quantitativen Handelsplattform des Erfinders Unterstützung für den nicht verpackten Zugriff auf die Exchange-API bereitzustellen. Nachdem das allgemeine Protokollprogramm ausgeführt wurde, starten Sie die Überwachung:http://127.0.0.1:6666Im allgemeinen Protokollprogramm können bestimmte Pfade angegeben werden, zum Beispiel/OKXbearbeitet werden.

3. Strategieinstanz fordert FMZ API-Funktion an

Wenn in der Strategie die (FMZ) Plattform-API-Funktion aufgerufen wird, erhält das Universal Protocol Program eine Anfrage vom Depotverwalter. Sie können zum Testen auch die Debugging-Tools der Plattform verwenden, zum Beispiel:

Seite mit Debugging-Tools:

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

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

Anrufexchange.GetTicker()Funktion, das allgemeine Protokollprogramm empfängt die Anfrage:

POST /OKX HTTP/1.1 
{
    "access_key":"xxx",
    "method":"ticker",
    "nonce":1730275031047002000,
    "params":{"symbol":"LTC_USDT"},
    "secret_key":"xxx"
}
  • access_key: Der in der Plattform “Exchange konfigurieren” oben konfigurierte Exchange-Schlüssel
  • secret_key: Der in der Plattform “Austausch konfigurieren” oben konfigurierte Austauschschlüssel
  • Methode: Bezieht sich auf die aufrufende Schnittstelle in der Strategie,exchange.GetTicker()Stunde,methodDas heißtticker
  • Nonce: Der Zeitstempel, wann die Anfrage aufgetreten ist.
  • Parameter: Parameter im Zusammenhang mit Schnittstellenaufrufen in der Richtlinie.exchange.GetTicker()Beim Aufruf sind die relevanten Parameter:{"symbol":"LTC_USDT"}

4. Allgemeines Protokoll Programmzugriff auf die Exchange-Schnittstelle

Wenn das allgemeine Protokollprogramm eine Anfrage vom Verwahrer erhält, kann es anhand der in der Anfrage enthaltenen Informationen Informationen wie die von der Strategie angeforderte Plattform-API-Funktion (einschließlich Parameterinformationen), den Austauschschlüssel usw. abrufen.

Basierend auf diesen Informationen kann das allgemeine Protokollprogramm auf die Austauschschnittstelle zugreifen, um die erforderlichen Daten abzurufen oder bestimmte Operationen auszuführen.

Normalerweise verfügt die Austauschschnittstelle über Methoden wie GET/POST/PUT/DELETE, die in öffentliche und private Schnittstellen unterteilt sind.

  • Öffentliche Schnittstelle: Eine Schnittstelle, die keine Signaturüberprüfung erfordert und direkt in einem allgemeinen Protokollprogramm angefordert wird.
  • Private Schnittstelle: eine Schnittstelle, die eine Signaturüberprüfung erfordert. Die Signatur muss im allgemeinen Protokollprogramm implementiert werden, um die API-Schnittstelle dieser Börsen anzufordern.

Das allgemeine Protokollprogramm empfängt die Antwortdaten der Austauschschnittstelle, verarbeitet sie weiter und baut sie in die vom Verwahrer erwarteten Daten um (siehe unten). Siehe OKX-Spotbörse, Implementierung der Klasse CustomProtocolOKX im allgemeinen Python-ProtokollbeispielGetTickerGetAccountUnd andere Funktionen.

5. Das allgemeine Protokollprogramm antwortet dem Verwalter

Wenn das allgemeine Protokollprogramm auf die API-Schnittstelle der Börse zugreift, bestimmte Vorgänge ausführt oder bestimmte Daten erhält, muss es die Ergebnisse an den Verwahrer zurückmelden.

Die an den Verwahrer zurückgemeldeten Daten variieren je nach der von der Strategie aufgerufenen Schnittstelle und werden zunächst in zwei Kategorien unterteilt:

  • Das allgemeine Protokollprogramm ruft die Austauschschnittstelle erfolgreich auf:
  {
      "data": null,  // "data" can be of any type 
      "raw": null    // "raw" can be of any type 
  }
  • Daten: Die spezifische Struktur dieses Felds ist dieselbe wie in der vom allgemeinen Protokollprogramm empfangenen Anforderung.methodNachfolgend finden Sie eine Liste aller Schnittstellen, die zum Erstellen der von der API-Funktion der FMZ-Plattform zurückgegebenen Datenstruktur verwendet werden.

  • Raw: In diesem Feld können die Rohdaten der Exchange-API-Antwort übergeben werden, beispielsweiseexchange.GetTicker()Die von der Funktion zurückgegebene Tickerstruktur enthält die folgenden im Infofeld der Tickerstruktur aufgezeichneten Informationen:rawFelder unddataDie Daten des Feldes; einige Plattform-API-Funktionen benötigen diese Daten nicht.

  • Der Aufruf der Austauschschnittstelle durch das allgemeine Protokollprogramm ist fehlgeschlagen (Geschäftsfehler, Netzwerkfehler usw.).

  {
      "error": ""    // "error" contains an error message as a string
  }
  • Fehler: Fehlerinformationen, die im Fehlerprotokoll im Protokollbereich der (FMZ)-Plattform-Realdiskette, im Debugging-Tool und auf anderen Seiten angezeigt werden.

Demonstriert die allgemeinen Protokollantwortdaten, die vom Richtlinienprogramm empfangen wurden:

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

Inventor Quantitative Trading Platform – Allgemeiner Protokollzugriffsleitfaden

6. Datenstrukturvereinbarung im allgemeinen Protokoll

Oben ist ein kurzer Prozess beschrieben, wie das allgemeine Protokollprogramm am Zugriff auf die (FMZ-unverpackte) Exchange-API teilnimmt. Dieser Prozess erklärt nur, wie das (FMZ-)Plattform-Debugging-Tool aufgerufen wird.exchange.GetTicker()Funktionsablauf. Als Nächstes werden die Interaktionsdetails aller API-Funktionen der Plattform ausführlich erläutert.

Die Plattform kapselt die gemeinsamen Funktionen verschiedener Börsen und vereinheitlicht sie in einer bestimmten Funktion, wie beispielsweise der GetTicker-Funktion, die die aktuellen Marktinformationen eines bestimmten Produkts anfordert. Dies ist im Grunde eine API, über die alle Börsen verfügen. Wenn also in der Strategieinstanz auf die in der Plattform gekapselte API-Schnittstelle zugegriffen wird, sendet der Verwahrer eine Anfrage an das oben erwähnte Plug-In „Universal Protocol“:

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

Beim Aufrufen verschiedener in der Inventor-Plattform gekapselter API-Funktionen in der Strategie (wie z. B. GetTicker) ist auch das vom Verwahrer an das allgemeine Protokoll gesendete Anforderungsformat unterschiedlich. Die Daten (JSON) im Body unterscheiden sich nur inmethodUndparams. Bei der Entwicklung eines allgemeinen ProtokollsmethodSie können je nach Inhalt bestimmte Vorgänge ausführen. Nachfolgend sind Anforderungs-Antwort-Szenarien für alle Schnittstellen aufgeführt.

Spot-Börse

Das aktuelle Handelspaar ist beispielsweise:ETH_USDT, auf die Einzelheiten werde ich später nicht näher eingehen. Die Daten, von denen der Verwahrer erwartet, dass das allgemeine Protokoll darauf antwortet, werden hauptsächlich in das Datenfeld geschrieben. Außerdem kann ein Rohfeld hinzugefügt werden, um die Originaldaten der Austauschschnittstelle aufzuzeichnen.

  • GetTicker

    • Methodenfeld: „Ticker“
    • Parameterfeld:
    {"symbol":"ETH_USDT"}
    
    • Daten, die der Host in der allgemeinen Protokollantwort erwartet:
    {
        "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

    • Methodenfeld: „Tiefe“
    • Parameterfeld:
    {"limit":"30","symbol":"ETH_USDT"}
    
    • Daten, die der Host in der allgemeinen Protokollantwort erwartet:
    {
        "data" : {
            "time" : 1500793319499,
            "asks" : [
                [1000, 0.5], [1001, 0.23], [1004, 2.1]
                // ... 
            ],
            "bids" : [
                [999, 0.25], [998, 0.8], [995, 1.4]
                // ... 
            ]
        }
    }
    
  • GetTrades

    • Methodenfeld: „Trades“
    • Parameterfeld:
    {"symbol":"eth_usdt"}
    
    • Daten, die der Host in der allgemeinen Protokollantwort erwartet:
    { 
        "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

    • Methodenfeld: „Datensätze“
    • Parameterfeld:
    {
        "limit":"500",
        "period":"60",          // 60分钟
        "symbol":"ETH_USDT"
    }
    
    • Daten, die der Host in der allgemeinen Protokollantwort erwartet:
    {
        "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 Umzusetzen

    • Methodenfeld: “”
    • Parameterfeld:
    {}
    
    • Daten, die der Host in der allgemeinen Protokollantwort erwartet:
    {}
    
  • GetTickers Umzusetzen

    • Methodenfeld: “”
    • Parameterfeld:
    {}
    
    • Daten, die der Host in der allgemeinen Protokollantwort erwartet:
    {}
    
  • GetAccount

    • Methodenfeld: „Konten“
    • Parameterfeld:
    {}
    
    • Daten, die der Host in der allgemeinen Protokollantwort erwartet:
    {
        "data": [
            {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
            {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
            // ...
        ]
    }
    
  • GetAssets

    • Methodenfeld: „Assets“
    • Parameterfeld:
    {}
    
    • Daten, die der Host in der allgemeinen Protokollantwort erwartet:
    {
        "data": [
            {"currency": "TUSD", "free": "3000", "frozen": "0"},
            {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
            // ...
        ]
    }
    
  • CreateOrder / Buy / Sell

    • Methodenfeld: „trade“
    • Parameterfeld:
    {"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
    
    • Daten, die der Host in der allgemeinen Protokollantwort erwartet:
    {
        "data": {
            "id": "BTC-USDT,123456"
        }
    }
    
  • GetOrders

    • Methodenfeld: „Bestellungen“
    • Parameterfeld:
    {"symbol":"ETH_USDT"}
    
    • Daten, die der Host in der allgemeinen Protokollantwort erwartet:
    {
        "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

    • Methodenfeld: „order“
    • Parameterfeld:
    {
        "id":"ETH-USDT,123456",       // 策略中调用:exchange.GetOrder("ETH-USDT,123456")
        "symbol":"ETH_USDT"
    }
    
    • Daten, die der Host in der allgemeinen Protokollantwort erwartet:
    { 
        "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

    • Methodenfeld: „historyorders“
    • Parameterfeld:
    {"limit":0,"since":0,"symbol":"ETH_USDT"}
    
    • Daten, die der Host in der allgemeinen Protokollantwort erwartet:
    {
        "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

    • Methodenfeld: „Abbrechen“
    • Parameterfeld:
    {"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
    
    • Daten, die der Host in der allgemeinen Protokollantwort erwartet:
    {
        "data": true    // 只要该JSON中没有error字段,都默认为撤单成功
    }
    
  • IO

Die Funktion exchange.IO wird verwendet, um direkt auf die Exchange-Schnittstelle zuzugreifen. Beispielsweise verwenden wirGET /api/v5/trade/orders-pending, 参数:instType=SPOT,instId=ETH-USDTZum Beispiel.

  // 策略实例中调用
  exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")
  • Methodenfeld:"__api_/api/v5/trade/orders-pending"Das Methodenfeld beginnt mit _api, was darauf hinweist, dass dies durch den Funktionsaufruf „exchange.IO“ in der Strategieinstanz ausgelöst wird.

  • Parameterfeld:

    {"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
    
  • Daten, die der Host in der allgemeinen Protokollantwort erwartet:

    {
        "data": {"code": "0", "data": [], "msg": ""}    // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
    }
    
  • andere Andere in den Strategiebeispielen verwendete Inventor Platform API-Funktionen, wie etwa: exchange.Go()exchange.GetRawJSON()Funktionen wie diese müssen nicht gekapselt werden, und die aufrufende Methode und Funktionalität bleiben unverändert.

Terminbörse

Terminbörsen unterstützen nicht nur alle Funktionen von Spotbörsen, sondern verfügen auch über einige API-Funktionen, die nur Terminbörsen bieten.

Umzusetzen

  • GetPositions
  • SetMarginLevel
  • GetFundings

Python-Version des allgemeinen Protokollbeispiels

Allgemeines REST-Protokoll – Zugriff auf die REST-API-Schnittstelle der OKX-Börse und deren Kapselung als Spot-Börsenobjekt. Implementierung einer gemeinsamen Schnittstelle zur Kapselung von Anforderungs- und Antwortdaten. Implementierung einer privaten Schnittstellensignatur sowie einer Kapselung von Anforderungs- und Antwortdaten. Dieses Beispiel dient hauptsächlich zum Testen und Lernen. Die anderen Schnittstellen verwenden simulierte Daten, um zu Testzwecken direkt auf den Host zu antworten.

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