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

発明者定量化取引プラットフォームの通用プロトコルへのアクセスガイド

作者: リン・ハーン発明者 量化 - 微かな夢作成日:2024年10月29日 14:37:56 更新日:2024年11月12日 21時58分55秒

[TOC]

img

発明者量化取引プラットフォームは,多数の仮想通貨取引所をサポートし,市場における主流取引所を包み込む.しかし,まだ多くの取引所が包み込まれていない.これらの取引所を使用する必要があるユーザーには,発明者量化通用プロトコルを通じてアクセスできます.REST協定やFIX協定のプラットフォームにもアクセスできます.

この記事ではRESTプロトコルアクセスの例として,発明者の量化取引プラットフォームの汎用プロトコルを使用してOKX取引所のAPIを包み込み,アクセスする方法について説明します.

  • 一般協定の作業流程は以下の通りです. 要求プロセス:管理者上で実行されるポリシーインスタンス -> 汎用プロトコル手順 -> 取引所API 応答プロセス: 取引所のAPI -> 汎用プロトコルプログラム -> 管理者上で実行されるポリシーインスタンス

1 取引所を配置する

発明者による量化取引プラットフォームの設定のページ:

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

img

  • 協定を選択する: "一般協定"を選択します.
  • サービスアドレス: UTP は本質的に RPC サービスです 取引所を構成する際に,サービスアドレス,ポートを明確に指定する必要があります.托管者上运行的策略实例 -> 通用协议程序このプロセスは,管理者が通用プロトコル手順にアクセスする場所を把握するプロセスです. 例えば:http://127.0.0.1:6666/OKX一般的には,GPUプログラムとホストが同じデバイス (サーバー) で実行されるので,サービスアドレスは本機 (localhost) に書き込まれ,ポートはシステムで占拠されていないものを使います.
  • アクセスキー:托管者上运行的策略实例 -> 通用协议程序このプロセスは,送信された取引所の配置情報を処理します.
  • 秘密鍵:托管者上运行的策略实例 -> 通用协议程序このプロセスは,送信された取引所の配置情報を処理します.
  • タグ付け: 発明者の量化取引プラットフォーム上の取引所のオブジェクトラベル,ある取引所のオブジェクトを識別するために使用される.

OKXプラグインの設定のスクリーンショットが公開されています.

img

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

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

2 管理者とプラグインを展開する

  • 1 管理者 発明者の量化取引プラットフォームで実行する任意の戦略実態盤には,特定の展開管理者が参照できるプラットフォームチュートリアルを参照できる托管者を展開する必要があります.

  • 2 汎用プロトコル (plugin) ホストとGPRは通常同じデバイスに展開され,GPR (サービス) プログラムは任意の言語で設計を書き,この記事ではPython3で書き,任意のPythonプログラムを実行するように直接実行することができます. FMZではもちろん,pythonプログラムを実行することもサポートされています. また,この汎用プロトコルをリアルディスクとして実行して,発明者に量化取引プラットフォームの非パッケージ化取引所APIへのアクセスをサポートすることができます. 通信通信は一般的プロトコルで実行され,http://127.0.0.1:6666特定のパス,例えば,PATH,PATH,PATH,PATH,PATH,PATH,PATH,PATHなど,/OKX処理は行われています.

3 策略例 FMZのAPI関数を要求する

ポリシーで (※FMZ) プラットフォームのAPI関数を呼び出すとき,UTPはホストからの要求を受け取ります.プラットフォームのデュッグツールを使用してテストすることもできます.例えば:

このページでは,デバッグツールについて説明します.

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

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

呼び出しexchange.GetTicker()函数,UTPは,次の要求を受けています:

POST /OKX HTTP/1.1 
{
    "access_key":"xxx",
    "method":"ticker",
    "nonce":1730275031047002000,
    "params":{"symbol":"LTC_USDT"},
    "secret_key":"xxx"
}
  • access_key: 上記記事でプラットフォーム"取引所設定"時に設定された取引所キー
  • secret_key: 上記記事でプラットフォーム"取引所設定"で設定された取引所キー
  • method: ポリシー内のインターフェースへの呼び出しに関連して,呼び出しexchange.GetTicker()メディアは,methodこれはticker
  • nonce: 要求の発生時の時間軸.
  • params: この例では,ポリシーにインターフェース呼び出しが起こると関連するパラメータexchange.GetTicker()呼び出し時に,関連するパラメータは:{"symbol":"LTC_USDT"}

4. 取引所のインターフェースへのアクセス

汎用プロトコルのプロセッサが托管者からのリクエストを受信すると,リクエストに載っている情報によって,政策要求のプラットフォームAPI関数 (パラメータ情報を含む) や取引所の秘密鍵などの情報を知ることができる.

この情報に基づいて,UTPは取引所のインターフェースにアクセスし,必要なデータを取得したり,特定の操作を実行することができます.

通常の取引所のインターフェースにはGET/POST/PUT/DELETEなどの方法があり,公共インターフェースとプライベートインターフェースに分かれます.

  • パブリックインターフェース:署名認証を必要としないインターフェース,通用プロトコル手順で直接要求される.
  • プライベートインターフェース:署名認証を必要とするインターフェース,UTPで署名を実現し,これらの取引所のAPIインターフェースを要求するインターフェース.

汎用プロトコルプロセスは,取引所のインターフェース応答データを受信し,さらに処理し,托管者が期待するデータを構成します (以下説明します). OKX 現場取引所参照, Python 汎用プロトコル例のCustomProtocol OKX クラスの実装GetTickerGetAccount等式関数である.

5 汎用プロトコルのプロセスは,データを托管者に返信します

汎用プロトコルのプログラムは,取引所のAPIインターフェイスにアクセスし,特定の操作を実行したり,特定のデータを取得した後,結果を托管者にフィードバックする必要がある.

管理者へのフィードバックデータは,ポリシーが呼び出されるインターフェースによって異なる.

  • 取引所のインターフェイスへの通用プロトコルによる呼び出しが成功しました.

    {
        "data": null,  // "data" can be of any type 
        "raw": null    // "raw" can be of any type 
    }
    
    • data: このフィールドの特定の構造と,通用プロトコルプロセスから受信された要求methodFMZプラットフォームのAPI関数で最終的に返されるデータ構造を構成するために,すべてのインターフェースがリストされています.
    • raw: このフィールドは,e.g.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"))
}

img

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) を呼び出すとき,ホストがUTPに送信するリクエストの形式も異なる.methodそしてparams◎ 汎用プロトコルの設計は,methodインタフェースの要求応答シナリオは以下のとおりです.

現金取引所

例えば,現在の取引対は:ETH_USDT管理者は,UTPによる応答を期待するデータが主にdataフィールドに書き込まれ,また,RAWフィールドを追加して交換インターフェースの原始データを記録することもできます.

  • GetTicker をインストールする

    • method フィールド: ticker
    • Params フィールド:
      {"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 を取得する

    • method フィールド:depth
    • Params フィールド:
      {"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 は

    • method フィールド: trades
    • Params フィールド:
      {"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 を取得する

    • method フィールド:records
    • Params フィールド:
      {
          "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 を取得する実現する

    • 方法欄:""
    • Params フィールド:
      {}
      
    • 管理者が期待するデータとは,
      {}
      
  • GetTickers は実現する

    • 方法欄:""
    • Params フィールド:
      {}
      
    • 管理者が期待するデータとは,
      {}
      
  • アカウントを取得

    • method フィールド: accounts
    • Params フィールド:
      {}
      
    • 管理者が期待するデータとは,
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • GetAssets について

    • method フィールド: assets
    • Params フィールド:
      {}
      
    • 管理者が期待するデータとは,
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"},
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • CreateOrder / 購入 / 販売する

    • method フィールド:trade
    • Params フィールド:
      {"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
      
    • 管理者が期待するデータとは,
      {
          "data": {
              "id": "BTC-USDT,123456"
          }
      }
      
  • GetOrders を取得する

    • method フィールド:orders
    • Params フィールド:
      {"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 を取得する

    • method フィールド: order
    • Params フィールド:
      {
          "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 を取得する

    • method フィールド: historyorders
    • Params フィールド:
      {"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"
              }, 
              // ...
          ]
      }
      
  • キャンセル

    • method フィールド: 削除 削除
    • Params フィールド:
      {"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
      
    • 管理者が期待するデータとは,
      {
          "data": true    // 只要该JSON中没有error字段,都默认为撤单成功
      }
      
  • IO

    exchange.IO函数用于直接访问交易所接口,例如我们以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 関数の呼び出しによって引き起こされる.
    • Params フィールド:
      {"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
      
    • 管理者が期待するデータとは,
      {
          "data": {"code": "0", "data": [], "msg": ""}    // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
      }
      
  • ほか 策略例で使用されている他の発明者プラットフォーム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'
        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()

もっと