Inventor Quantitative Trading Platform の一般的なプロトコル アクセス ガイド

作成日:: 2024-10-29 14:37:56, 更新日:: 2024-11-12 21:58:55
comments   0
hits   302

[TOC]

Inventor Quantitative Trading Platform の一般的なプロトコル アクセス ガイド

Inventor Quantitative Trading Platform は、多くの暗号通貨取引所をサポートし、市場の主流の取引所をカプセル化します。しかし、まだパッケージ化されていない取引所も多数存在します。これらの取引所を利用する必要があるユーザーは、発明者によって定量化されたユニバーサルプロトコルを通じてアクセスできます。暗号通貨取引所に限らず、REST同意またはFIX協定のプラットフォームにもアクセス可能です。

この記事はRESTプロトコル アクセスを例に、Inventor Quantitative Trading Platform の一般的なプロトコルを使用して OKX Exchange の API をカプセル化してアクセスする方法を説明します。特に指定がない限り、この記事では REST の一般的なプロトコルについて説明します。

  • 一般的なプロトコルのワークフローは次のとおりです。 リクエストプロセス: カストディアン上で実行されている戦略インスタンス -> 一般的なプロトコルプログラム -> Exchange API 応答プロセス: Exchange API -> 一般的なプロトコルプログラム -> カストディアン上で実行される戦略インスタンス

1. 取引所を設定する

Inventor Quantitative Trading Platform での取引所の設定ページ:

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

Inventor Quantitative Trading Platform の一般的なプロトコル アクセス ガイド

  • プロトコルを選択: 「一般プロトコル」を選択します。
  • サービスアドレス: 一般的なプロトコルプログラムは本質的にRPCサービスです そのため、交換を設定する際には、サービス アドレスとポートを明確に指定する必要があります。だから托管者上运行的策略实例 -> 通用协议程序プロセス中、ホストは共通プロトコル プログラムにアクセスする場所を認識します。 例えば:http://127.0.0.1:6666/OKX通常、共通プロトコルプログラムとホストは同一デバイス(サーバー)上で実行されるため、サービスアドレスはローカルマシン(localhost)と記述され、ポートはシステムが占有していないポートでも構いません。
  • Access Key: 托管者上运行的策略实例 -> 通用协议程序プロセス中に渡された交換構成情報。
  • Secret Key: 托管者上运行的策略实例 -> 通用协议程序プロセス中に渡された交換構成情報。
  • ラベル: Inventor Quantitative Trading Platform の交換オブジェクト ラベルは、特定の交換オブジェクトを識別するために使用されます。

記事で公開されている OKX プラグイン構成のスクリーンショットは次のとおりです。

Inventor Quantitative Trading Platform の一般的なプロトコル アクセス ガイド

OKX取引所の秘密鍵の設定情報:

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

2. カストディアンとユニバーサルプロトコルプログラム(プラグイン)の展開

    1. ホスト Inventor Quantitative Trading Platform でリアルタイム戦略を実行するには、カストディアンをデプロイする必要があります。カストディアンの具体的なデプロイについては、プラットフォーム チュートリアルを参照してください。ここでは繰り返しません。
    1. 汎用プロトコルプログラム(プラグイン) ホストとユニバーサル プロトコルは通常、同じデバイスに展開されます。ユニバーサル プロトコル (サービス) プログラムは任意の言語で記述できます。この記事は Python3 で記述されています。他の Python プログラムを実行する場合と同様に、直接実行できます (事前に Python 環境のさまざまな構成を作成します)。 もちろん、FMZ は Python プログラムの実行もサポートしており、この一般的なプロトコルを実際のディスクとして実行して、発明者の定量取引プラットフォームにパッケージ化されていない取引所 API アクセスのサポートを提供することもできます。 一般的なプロトコル プログラムが実行されたら、監視を開始します。http://127.0.0.1:6666一般的なプロトコルプログラムでは、特定のパスを指定することができます。たとえば、/OKX処理されます。

3. 戦略インスタンスがFMZ API関数を要求する

戦略で (FMZ) プラットフォーム API 関数が呼び出されると、ユニバーサル プロトコル プログラムは管理者からの要求を受け取ります。プラットフォームのデバッグ ツールを使用してテストすることもできます。例:

デバッグ ツール ページ:

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

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

電話exchange.GetTicker()関数、一般的なプロトコル プログラムは要求を受信します。

POST /OKX HTTP/1.1 
{
    "access_key":"xxx",
    "method":"ticker",
    "nonce":1730275031047002000,
    "params":{"symbol":"LTC_USDT"},
    "secret_key":"xxx"
}
  • access_key: 上記のプラットフォーム「Exchangeの設定」で設定された交換キー
  • secret_key: 上記のプラットフォーム「Exchangeの設定」で設定された交換キー
  • メソッド: 戦略の呼び出しインターフェースに関連し、呼び出しexchange.GetTicker()時間、methodつまりticker
  • nonce: リクエストが発生したときのタイムスタンプ。
  • params: ポリシー内のインターフェース呼び出しに関連するパラメータ。exchange.GetTicker()呼び出すときに関連するパラメータは次のとおりです。{"symbol":"LTC_USDT"}

4. 交換インターフェースへの汎用プロトコルプログラムアクセス

汎用プロトコルプログラムは、カストディアンからのリクエストを受信すると、リクエストに含まれる情報に基づいて、戦略が要求するプラットフォームAPI関数(パラメータ情報を含む)、交換キーなどの情報を取得できます。

この情報に基づいて、一般的なプロトコル プログラムは交換インターフェイスにアクセスして必要なデータを取得したり、特定の操作を実行したりできます。

通常、交換インターフェースには GET/POST/PUT/DELETE などのメソッドがあり、これらはパブリック インターフェースとプライベート インターフェースに分かれています。

  • パブリック インターフェイス: 署名検証を必要とせず、一般的なプロトコル プログラムで直接要求されるインターフェイス。
  • プライベート インターフェース: 署名検証を必要とするインターフェース。これらの交換の API インターフェースを要求するには、一般的なプロトコル プログラムに署名を実装する必要があります。

一般的なプロトコル プログラムは、交換インターフェイス応答データを受信し、それをさらに処理して、管理者が期待するデータに構築します (以下で説明)。 OKXスポット取引所、Pythonの一般的なプロトコル例でのCustomProtocolOKXクラスの実装を参照してください。GetTickerGetAccountそしてその他の機能。

5. 一般的なプロトコルプログラムはデータを管理者に応答する

一般的なプロトコル プログラムが取引所の API インターフェイスにアクセスし、特定の操作を実行したり、特定のデータを取得したりする場合、その結果を管理者にフィードバックする必要があります。

カストディアンにフィードバックされるデータは、戦略によって呼び出されるインターフェースによって異なり、まず 2 つのカテゴリに分類されます。

  • 一般的なプロトコル プログラムは、交換インターフェイスを正常に呼び出します。
  {
      "data": null,  // "data" can be of any type 
      "raw": null    // "raw" can be of any type 
  }
  • データ: このフィールドの具体的な構造は、一般的なプロトコル プログラムによって受信される要求の構造と同じです。method以下は、FMZ プラットフォーム API 関数によって返されるデータ構造を構築するために使用されるすべてのインターフェイスのリストです。

  • Raw: このフィールドは、Exchange APIレスポンスの生データを渡すために使用できます。例:exchange.GetTicker()関数によって返される Ticker 構造体には、Ticker 構造体の Info フィールドに記録された次の情報が含まれます。rawフィールドとdataフィールドのデータ。一部のプラットフォーム API 関数ではこのデータは必要ありません。

  • 一般的なプロトコル プログラムが交換インターフェイスの呼び出しに失敗しました (ビジネス エラー、ネットワーク エラーなど)

  {
      "error": ""    // "error" contains an error message as a string
  }
  • error: エラー情報。(FMZ)プラットフォームの実ディスク、デバッグツール、その他のページのログ領域のエラーログに表示されます。

ポリシー プログラムによって受信される一般的なプロトコル応答データを示します。

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

Inventor Quantitative Trading Platform の一般的なプロトコル アクセス ガイド

6. 一般的なプロトコルにおけるデータ構造の合意

上記は、一般的なプロトコル プログラムが (FMZ のパッケージ化されていない) 交換 API へのアクセスに参加する方法の簡単なプロセスです。このプロセスでは、(FMZ) プラットフォーム デバッグ ツールを呼び出す方法のみを説明しています。exchange.GetTicker()関数プロセス。次に、すべてのプラットフォーム API 関数の相互作用の詳細について詳しく説明します。

このプラットフォームは、さまざまな取引所の共通機能をカプセル化し、特定の機能に統合します。たとえば、特定の商品の現在の市場情報を要求する GetTicker 機能などです。これは基本的にすべての取引所が持つ API です。したがって、プラットフォームのカプセル化された API インターフェイスが戦略インスタンスでアクセスされると、管理者は「ユニバーサル プロトコル」プラグイン (上記) にリクエストを送信します。

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

戦略内で異なる発明者プラットフォームのカプセル化された API 関数 (GetTicker など) を呼び出す場合、管理者が一般プロトコルに送信する要求形式も異なります。本文中のデータ(JSON)は、methodそしてparams。一般的なプロトコルを設計する場合、methodコンテンツに応じて特定の操作を実行できます。以下は、すべてのインターフェースの要求応答シナリオです。

スポット取引

たとえば、現在の取引ペアは次のとおりです。ETH_USDT、後ほど詳細には触れません。管理者が一般的なプロトコルが応答することを期待するデータは主にデータ フィールドに書き込まれ、交換インターフェースの元のデータを記録するために raw フィールドを追加することもできます。

  • GetTicker

    • メソッドフィールド: “ticker”
    • パラメータフィールド:
    {"symbol":"ETH_USDT"}
    
    • ホストが一般的なプロトコル応答で期待するデータ:
    {
        "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

    • メソッドフィールド: “depth”
    • パラメータフィールド:
    {"limit":"30","symbol":"ETH_USDT"}
    
    • ホストが一般的なプロトコル応答で期待するデータ:
    {
        "data" : {
            "time" : 1500793319499,
            "asks" : [
                [1000, 0.5], [1001, 0.23], [1004, 2.1]
                // ... 
            ],
            "bids" : [
                [999, 0.25], [998, 0.8], [995, 1.4]
                // ... 
            ]
        }
    }
    
  • GetTrades

    • メソッドフィールド: “trades”
    • パラメータフィールド:
    {"symbol":"eth_usdt"}
    
    • ホストが一般的なプロトコル応答で期待するデータ:
    { 
        "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

    • メソッドフィールド: “records”
    • パラメータフィールド:
    {
        "limit":"500",
        "period":"60",          // 60分钟
        "symbol":"ETH_USDT"
    }
    
    • ホストが一般的なプロトコル応答で期待するデータ:
    {
        "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 実施予定

    • メソッドフィールド: “”
    • パラメータフィールド:
    {}
    
    • ホストが一般的なプロトコル応答で期待するデータ:
    {}
    
  • GetTickers 実施予定

    • メソッドフィールド: “”
    • パラメータフィールド:
    {}
    
    • ホストが一般的なプロトコル応答で期待するデータ:
    {}
    
  • GetAccount

    • メソッドフィールド: “accounts”
    • パラメータフィールド:
    {}
    
    • ホストが一般的なプロトコル応答で期待するデータ:
    {
        "data": [
            {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
            {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
            // ...
        ]
    }
    
  • GetAssets

    • メソッドフィールド: “assets”
    • パラメータフィールド:
    {}
    
    • ホストが一般的なプロトコル応答で期待するデータ:
    {
        "data": [
            {"currency": "TUSD", "free": "3000", "frozen": "0"},
            {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
            // ...
        ]
    }
    
  • CreateOrder / Buy / Sell

    • メソッドフィールド: “trade”
    • パラメータフィールド:
    {"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
    
    • ホストが一般的なプロトコル応答で期待するデータ:
    {
        "data": {
            "id": "BTC-USDT,123456"
        }
    }
    
  • GetOrders

    • メソッド フィールド: “orders”
    • パラメータフィールド:
    {"symbol":"ETH_USDT"}
    
    • ホストが一般的なプロトコル応答で期待するデータ:
    {
        "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

    • メソッドフィールド: “order”
    • パラメータフィールド:
    {
        "id":"ETH-USDT,123456",       // 策略中调用:exchange.GetOrder("ETH-USDT,123456")
        "symbol":"ETH_USDT"
    }
    
    • ホストが一般的なプロトコル応答で期待するデータ:
    { 
        "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

    • メソッド フィールド: “historyorders”
    • パラメータフィールド:
    {"limit":0,"since":0,"symbol":"ETH_USDT"}
    
    • ホストが一般的なプロトコル応答で期待するデータ:
    {
        "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

    • メソッドフィールド: “キャンセル”
    • パラメータフィールド:
    {"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
    
    • ホストが一般的なプロトコル応答で期待するデータ:
    {
        "data": true    // 只要该JSON中没有error字段,都默认为撤单成功
    }
    
  • IO

exchange.IO関数は、exchangeインターフェースに直接アクセスするために使用されます。たとえば、次のように使用します。GET /api/v5/trade/orders-pending, 参数:instType=SPOT,instId=ETH-USDT例えば。

  // 策略实例中调用
  exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")
  • メソッドフィールド:"__api_/api/v5/trade/orders-pending"メソッド フィールドは _api で始まり、これが戦略インスタンス内の exchange.IO 関数呼び出しによってトリガーされることを示します。

  • パラメータフィールド:

    {"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
    
  • ホストが一般的なプロトコル応答で期待するデータ:

    {
        "data": {"code": "0", "data": [], "msg": ""}    // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
    }
    
  • 他の 戦略例で使用されるその他の Inventor プラットフォーム API 関数: exchange.Go()exchange.GetRawJSON()このような関数はカプセル化する必要がなく、呼び出し方法と機能は変更されません。

先物取引所

先物取引所では、現物取引所のすべての機能をサポートしているほか、先物取引所独自の API 機能もいくつかあります。

実施予定

  • GetPositions
  • SetMarginLevel
  • GetFundings

一般的なプロトコル例のPythonバージョン

REST 一般プロトコル - OKX 取引所 REST API インターフェースにアクセスし、それをスポット取引所オブジェクトとしてカプセル化します。 リクエストとレスポンスのデータのカプセル化のための共通インターフェースを実装しました。 プライベート インターフェイス署名、要求および応答データのカプセル化を実装しました。 この例は主にテストと学習用です。他のインターフェースは、テストのためにシミュレートされたデータを使用してホストに直接応答します。

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