資源の読み込みに... 荷物...

FMZ 量子取引プラットフォーム カスタム プロトコル アクセスガイド

作者: リン・ハーンFMZ~リディア, 作成日:2024年11月08日 16:37:25 更新日:2024年11月08日 16:38:52

We FMZ Quant Trading Platformは,多くの仮想通貨取引所をサポートし,市場の主流取引所をカプセル化しています.しかし,まだカプセル化されていない多くの取引所があります.これらの取引所を使用する必要があるユーザーには,FMZ Quant Custom Protocolを通じてアクセスできます.仮想通貨取引所に限らず,REST議定書またはFIXプロトコルもアクセスできます

この 記事 はREST標準化プロトコルは,FMZ Quant Trading Platformのカスタムプロトコルを使用してOKX取引所のAPIをカプセル化し,アクセスする方法を説明する例として使用する.別の規定がない限り,この記事はRESTカスタムプロトコルを参照します.

  • カスタムプロトコルのワークフローは: リクエストプロセス: ドッカーで実行されている戦略インスタンスの -> オーダーメイドプロトコルプログラム -> Exchange API 応答プロセス: Exchange API -> カスタム プロトコル プログラム -> Docker で実行されている 戦略 インスタンツ

1. エクスチェンジを設定する

FMZ 量子取引プラットフォームの取引所の設定ページ:

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

img

  • プロトコルを選択: カスタム プロトコルを選択します.
  • サービスアドレス:カスタムプロトコルプログラムは本質的に RPC サービスです. 交換を構成する際に,サービスアドレスとポートを明確に指定することが必要です.この方法で,ドーカーはプロセス中にカスタムプロトコルプログラムにアクセスする場所を知っています.Strategy instance running on the docker -> Custom protocol program- わかった 例えば:http://127.0.0.1:6666/OKX,カスタムプロトコルプログラムとドーカーは通常同じデバイス (サーバー) で実行されるため,サービスアドレスはローカルマシン (localhost) として書き込まれ,ポートはシステムに占用されていないポートとして使用できます.
  • アクセスキー: プロセス中に転送された交換構成情報Strategy instance running on the docker -> Custom protocol program.
  • 秘密鍵: プロセス中に転送された交換構成情報Strategy instance running on the docker -> Custom protocol program.
  • タグ: FMZ量子取引プラットフォーム上の交換対象のタグで,特定の交換対象を識別するために使用されます.

OKXプラグインの設定のスクリーンショットは以下のとおりです.

img

OKX交換秘密鍵の設定情報:

accessKey:  accesskey123    // accesskey123, these are not actual keys, just for demonstration
secretKey:  secretkey123
passphrase: passphrase123

2. ドッカーとカスタムプロトコルプログラムの展開 (プラグイン)

    1. ドッカー FMZ Quant トレーディング プラットフォーム上のどの戦略も実行するには,docker がデプロイする必要があります.docker をデプロイする方法の詳細については,プラットフォームチュートリアルを参照してください.ここでは詳細には入っていません.
    1. カスタム プロトコル プログラム (プラグイン) Docker とカスタムプロトコルは通常同じデバイスに展開される.カスタムプロトコル (サービス) プログラムは任意の言語で記述することができます.この記事は Python で書かれています.任意の Python プログラムを実行するように,直接実行できます ( Python 環境のさまざまな設定を事前に行う). FMZ Quant Trading Platformには,非パッケージ化交換 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: 上のプラットフォームで設定された交換鍵
  • secret_key: 上のプラットフォームで設定された交換鍵
  • 方法: 戦略内の呼び出しインターフェースに関連して,呼び出すときexchange.GetTicker(), methodticker.
  • nonce: 要求が発生した時のタイムスタンプ.
  • パラメータ: この例では,呼び出し時に,戦略でインターフェース呼び出しに関連するパラメータexchange.GetTicker()関連パラメータは:{"symbol":"LTC_USDT"}.

4. エクスチェンジ インターフェイスへのカスタムプロトコル プログラム アクセス

カスタムプロトコルプログラムがドッカーからのリクエストを受信すると,リクエストに含まれる情報に基づいて,戦略が要求するプラットフォーム API 機能 (パラメータ情報を含む) や交換鍵などの情報を取得することができます.

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

通常,交換インターフェイスには,公開インターフェイスとプライベートインターフェイスに分かれているGET/POST/PUT/DELETEなどのメソッドがあります.

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

カスタムプロトコルプログラムは,交換インターフェイスから応答データを受信し,ドッカーが期待するデータを構築するためにそれを処理します. OKXスポット交換を参照してください.GetTicker, GetAccountPythonのカスタムプロトコル例の CustomProtocolOKX クラス実装の他の関数.

5. カスタム プロトコル プログラムは,ドッカーへのデータに対応

カスタムプロトコルプログラムが Exchange の API インターフェースにアクセスし,特定の操作を実行したり,特定のデータを取得するとき,結果をドッカーにフィードバックする必要があります.

戦略によって呼び出されたインターフェースに応じて,ドッカーに返信されるデータは異なる.

  • カスタム プロトコル プログラムは 交換 インターフェイスを成功 呼び出し:
{
    "data": null,  // "data" can be of any type 
    "raw": null    // "raw" can be of any type 
}

このフィールドの特異的な構造はmethodFMZ プラットフォーム API 機能によって最終的に返されるデータ構造を構築するために使用される.すべてのインターフェースは以下にリストされます. raw: このフィールドは,交換 API インタフェース応答の生データ,例えば,TCKER 構造が返されるデータを通すのに使用できます.exchange.GetTicker()機能. ティッカーの構造のInfoフィールドは,rawフィールドとdataプラットフォームの API 機能のいくつかはこのデータを必要としません.

  • カスタム プロトコル プログラムは交換 インターフェースを呼び出すのに失敗しました (ビジネス エラー,ネットワーク エラー,など)
{
    "error": ""    // "error" contains an error message as a string
}

エラー: エラー ログに表示される エラー 情報は, (FMZ) プラットフォームのライブ取引,デバッグ ツール,その他のページのログ エリアに表示されます.

戦略プログラムによって受信されたカスタムプロトコル応答データを示します.

// Tested in the debugging tool of the FMZ platform
function main() {
    Log(exchange.GetTicker("USDT"))       // The trading pair is incomplete, the BaseCurrency part is missing, and the custom protocol plug-in is required to return an error message: {"error": "..."}
    Log(exchange.GetTicker("LTC_USDT"))
}

img

6. カスタムプロトコルにおけるデータ構造協定

上記は, (FMZ unpackaged) 交換 API にアクセスするカスタムプロトコル プログラムの短いプロセスです.このプロセスは,ただ,呼び出すときのプロセスを説明しますexchange.GetTicker()(FMZ) プラットフォームデバッグツールで機能します.以下は,すべてのプラットフォーム API 機能の相互作用の詳細を詳細に説明します.

このプラットフォームは,さまざまな取引所の共通の機能をカプセル化し,特定の製品の現在の市場情報を要求するGetTicker機能などの特定の機能に統一します.これは基本的にすべての取引所が持っているAPIです.したがって,戦略インスタンスのプラットフォームによってカプセル化されたAPIインターフェースにアクセスする際に,ドッカーがCustom Protocolプラグインプログラム (上記) に要求を送信します.

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

戦略 (GetTicker など) で異なる FMZ プラットフォームがカプセル化された API 関数を呼び出すとき,ドッカーがカスタム プロトコルに送信するリクエスト フォーマットも異なる.ボディ内のデータ (JSON) は,methodそしてparams. カスタムプロトコルを設計するとき,メソッドの内容に応じて特定の操作を実行します. 以下はすべてのインターフェースのリクエスト応答シナリオです.

スポット交換

例えば,現在の取引ペアは,ETH_USDTドッカーがカスタムプロトコルの応答を期待するデータは主にデータフィールドに書き込まれ,交換インターフェースの元のデータを記録するために生フィールドも追加できます.

  • GetTicker をインストールする

方法フィールド: ticker パラームフィールド:

{"symbol":"ETH_USDT"}

ドーカーがカスタムプロトコル応答で期待するデータ:

{
    "data": {
        "symbol": "ETH_USDT",      // Corresponds to the Symbol field in the Ticker structure returned by the GetTicker function
        "buy": "2922.18",          // ...corresponds to the Buy field
        "sell": "2922.19", 
        "high": "2955", 
        "low": "2775.15", 
        "open": "2787.72", 
        "last": "2922.18", 
        "vol": "249400.888156", 
        "time": "1731028903911"
    },
    "raw": {}                      // A raw field can be added to record the raw data of the exchange API interface response
}
  • GetDepth を取得する

方法フィールド: 深さ パラームフィールド:

{"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 は

方法欄: 取引 パラームフィールド:

{"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 を取得する

メソッドフィールド: 記録 パラームフィールド:

{
    "limit":"500",
    "period":"60",          // 60 minutes
    "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 は実施する

方法フィールド: パラームフィールド:

{}

ドーカーがカスタムプロトコル応答で期待するデータ:

{}
  • アカウントを取得

方法フィールド: 口座 パラームフィールド:

{}

ドーカーがカスタムプロトコル応答で期待するデータ:

{
    "data": [
        {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
        {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
        // ...
    ]
}
  • GetAssets について

方法フィールド: 資産 パラームフィールド:

{}

ドーカーがカスタムプロトコル応答で期待するデータ:

{
    "data": [
        {"currency": "TUSD", "free": "3000", "frozen": "0"},
        {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
        // ...
    ]
}
  • CreateOrder / 購入 / 販売する

方法フィールド: 取引 パラームフィールド:

{"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}

ドーカーがカスタムプロトコル応答で期待するデータ:

{
    "data": {
        "id": "BTC-USDT,123456"
    }
}
  • GetOrders を取得する

方法フィールド: 注文 パラームフィールド:

{"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 を取得する

方法フィールド: 順序 パラームフィールド:

{
    "id":"ETH-USDT,123456",       // Calling in the strategy: 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,         // If the exchange does not provide it, it can be assigned a value of 0 during processing.
    }
}
  • GetHistoryOrders を取得する

メソッドフィールド: 履歴注文 パラームフィールド:

{"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"
        }, 
        // ...
    ]
}
  • キャンセル

メソッドフィールド: キャンセル パラームフィールド:

{"id":"ETH-USDT,123456","symbol":"ETH_USDT"}

ドーカーがカスタムプロトコル応答で期待するデータ:

{
    "data": true    // As long as there is no error field in the JSON, the order cancellation is considered successful by default.
}
  • IO

についてexchange.IO交換インターフェースに直接アクセスするために使用されます.例えば,GET /api/v5/trade/orders-pending, parameters: instType=SPOT, instId=ETH-USDT例えば

// Called in the strategy instance
exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")

メソッドフィールド:"__api_/api/v5/trade/orders-pending"方法フィールドは _ から始まる.アピ発症する.exchange.IO戦略インスタンスの関数呼び出し パラームフィールド:

{"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT encoded parameters will be restored to JSON

ドーカーがカスタムプロトコル応答で期待するデータ:

{
    "data": {"code": "0", "data": [], "msg": ""}    // The data attribute value is the data of the exchange API: GET /api/v5/trade/orders-pending response
}
  • ほか 戦略例で使用される他のFMZプラットフォーム API機能,例えば:exchange.Go(), exchange.GetRawJSON()呼び出し方法と関数は変わらなくなります

フューチャー取引

スポット取引所のすべての機能をサポートするだけでなく,先物取引所は先物取引所に特有のAPI機能も備えています.

実施する

  • GetPositions を取得する
  • マージンレベルを設定
  • 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'
        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()

もっと