avatar of 发明者量化-小小梦 发明者量化-小小梦
fokus pada Pesan pribadi
4
fokus pada
1271
Pengikut

Panduan Akses Protokol Umum Platform Perdagangan Kuantitatif Inventor

Dibuat di: 2024-10-29 14:37:56, diperbarui pada: 2024-11-12 21:58:55
comments   0
hits   865

[TOC]

Panduan Akses Protokol Umum Platform Perdagangan Kuantitatif Inventor

Platform Perdagangan Kuantitatif Inventor mendukung banyak bursa mata uang kripto dan merangkum bursa utama di pasar. Namun, masih banyak bursa yang tidak dikemas. Bagi pengguna yang perlu menggunakan bursa ini, mereka dapat mengaksesnya melalui protokol universal yang dikuantifikasi oleh penemunya. Tidak terbatas pada pertukaran mata uang kripto, apa punRESTPersetujuan atauFIXPlatform perjanjian juga dapat diakses.

Artikel ini akanRESTMengambil akses protokol sebagai contoh, dijelaskan cara menggunakan protokol umum Platform Perdagangan Kuantitatif Inventor untuk merangkum dan mengakses API Bursa OKX. Kecuali dinyatakan lain, artikel ini mengacu pada protokol umum REST.

  • Alur kerja protokol umum adalah: Proses permintaan: Instansi strategi yang berjalan pada kustodian -> Program protokol umum -> API Exchange Proses respons: API Exchange -> Program protokol umum -> Instans strategi yang berjalan pada kustodian

1. Konfigurasikan pertukaran

Halaman untuk mengonfigurasikan pertukaran pada Platform Perdagangan Kuantitatif Inventor:

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

Panduan Akses Protokol Umum Platform Perdagangan Kuantitatif Inventor

  • Pilih protokol: Pilih “Protokol Umum”.
  • Alamat layanan: Program protokol umum pada dasarnya adalah layanan RPC Oleh karena itu, penting untuk menentukan alamat layanan dan port dengan jelas saat mengonfigurasi pertukaran. Jadi di托管者上运行的策略实例 -> 通用协议程序Selama proses berlangsung, host mengetahui di mana mengakses program protokol umum. Misalnya:http://127.0.0.1:6666/OKXBiasanya, program protokol umum dan host dijalankan pada perangkat yang sama (server), sehingga alamat layanan ditulis sebagai mesin lokal (localhost), dan port dapat berupa port yang tidak ditempati oleh sistem.
  • Access Key: 托管者上运行的策略实例 -> 通用协议程序Informasi konfigurasi pertukaran yang dikirimkan selama proses.
  • Secret Key: 托管者上运行的策略实例 -> 通用协议程序Informasi konfigurasi pertukaran yang dikirimkan selama proses.
  • Label: Label objek pertukaran pada Platform Perdagangan Kuantitatif Inventor digunakan untuk mengidentifikasi objek pertukaran tertentu.

Tangkapan layar konfigurasi plug-in OKX yang diungkapkan dalam artikel adalah sebagai berikut:

Panduan Akses Protokol Umum Platform Perdagangan Kuantitatif Inventor

Informasi konfigurasi kunci rahasia bursa OKX:

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

2. Penerapan kustodian dan program protokol universal (plugin)

    1. Tuan rumah Untuk menjalankan strategi real-time apa pun pada Inventor Quantitative Trading Platform, Anda harus menggunakan kustodian. Untuk penggunaan kustodian secara spesifik, silakan lihat tutorial platform, yang tidak akan diulang di sini.
    1. Program protokol umum (plugin) Host dan protokol universal biasanya digunakan pada perangkat yang sama. Program protokol universal (layanan) dapat ditulis dalam bahasa apa pun. Artikel ini ditulis dalam Python3. Sama seperti menjalankan program Python lainnya, Anda dapat mengeksekusinya secara langsung (membuat berbagai konfigurasi lingkungan Python terlebih dahulu). Tentu saja, FMZ juga mendukung jalannya program Python, dan protokol umum ini juga dapat dijalankan sebagai disk nyata untuk menyediakan platform perdagangan kuantitatif milik penemu dengan dukungan akses API bursa tanpa paket. Setelah program protokol umum berjalan, mulailah memantau:http://127.0.0.1:6666Dalam program protokol umum, jalur tertentu dapat ditentukan, misalnya/OKXuntuk diproses.

3. Permintaan instans strategi fungsi API FMZ

Ketika fungsi API platform (FMZ) dipanggil dalam strategi, Program Protokol Universal menerima permintaan dari kustodian. Anda juga dapat menguji menggunakan alat debugging platform, misalnya:

Halaman alat debugging:

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

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

Panggilanexchange.GetTicker()Fungsi, program protokol umum menerima permintaan:

POST /OKX HTTP/1.1 
{
    "access_key":"xxx",
    "method":"ticker",
    "nonce":1730275031047002000,
    "params":{"symbol":"LTC_USDT"},
    "secret_key":"xxx"
}
  • access_key: Kunci pertukaran yang dikonfigurasikan di platform “Konfigurasi Pertukaran” di atas
  • secret_key: Kunci pertukaran yang dikonfigurasikan di platform “Konfigurasi Pertukaran” di atas
  • metode: terkait dengan antarmuka pemanggilan dalam strategi, pemanggilanexchange.GetTicker()jam,methodYaituticker
  • nonce: Cap waktu saat permintaan terjadi.
  • params: Parameter yang terkait dengan panggilan antarmuka dalam kebijakan.exchange.GetTicker()Saat memanggil, parameter yang relevan adalah:{"symbol":"LTC_USDT"}

4. Akses program protokol umum ke antarmuka pertukaran

Ketika program protokol umum menerima permintaan dari kustodian, ia dapat memperoleh informasi seperti fungsi API platform (termasuk informasi parameter) yang diminta oleh strategi, kunci pertukaran, dll. berdasarkan informasi yang dibawa dalam permintaan.

Berdasarkan informasi ini, program protokol umum dapat mengakses antarmuka pertukaran untuk mendapatkan data yang diperlukan atau melakukan operasi tertentu.

Biasanya antarmuka pertukaran memiliki metode seperti GET/POST/PUT/DELETE, yang terbagi menjadi antarmuka publik dan antarmuka pribadi.

  • Antarmuka publik: antarmuka yang tidak memerlukan verifikasi tanda tangan dan diminta secara langsung dalam program protokol umum.
  • Antarmuka pribadi: antarmuka yang memerlukan verifikasi tanda tangan. Tanda tangan perlu diimplementasikan dalam program protokol umum untuk meminta antarmuka API dari pertukaran ini.

Program protokol umum menerima data respons antarmuka pertukaran, memprosesnya lebih lanjut, dan menyusunnya menjadi data yang diharapkan oleh kustodian (dijelaskan di bawah). Lihat pertukaran spot OKX, implementasi kelas CustomProtocolOKX dalam contoh protokol umum PythonGetTickerGetAccountDan fungsi lainnya.

5. Program protokol umum menanggapi kustodian

Saat program protokol umum mengakses antarmuka API bursa, melakukan operasi tertentu, atau memperoleh data tertentu, ia perlu memberikan umpan balik hasilnya kepada kustodian.

Data yang diberikan kembali ke kustodian bervariasi menurut antarmuka yang dipanggil oleh strategi, dan pertama-tama dibagi menjadi dua kategori:

  • Program protokol umum berhasil memanggil antarmuka pertukaran:
  {
      "data": null,  // "data" can be of any type 
      "raw": null    // "raw" can be of any type 
  }
  • data: Struktur spesifik bidang ini sama seperti pada permintaan yang diterima oleh program protokol umum.methodBerikut ini adalah daftar semua antarmuka yang digunakan untuk membangun struktur data yang dikembalikan oleh fungsi API platform FMZ.

  • Mentah: Bidang ini dapat digunakan untuk meneruskan data mentah dari respons API pertukaran, misalnyaexchange.GetTicker()Struktur Ticker yang dikembalikan oleh fungsi tersebut memiliki informasi berikut yang tercatat di bidang Info dari struktur Ticker:rawLapangan dandataData lapangan; beberapa fungsi API platform tidak memerlukan data ini.

  • Program protokol umum gagal memanggil antarmuka pertukaran (kesalahan bisnis, kesalahan jaringan, dll.)

  {
      "error": ""    // "error" contains an error message as a string
  }
  • kesalahan: informasi kesalahan, yang akan ditampilkan dalam log kesalahan di area log disk nyata platform (FMZ), alat debugging, dan halaman lainnya.

Menunjukkan data respons protokol umum yang diterima oleh program kebijakan:

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

Panduan Akses Protokol Umum Platform Perdagangan Kuantitatif Inventor

6. Perjanjian struktur data dalam protokol umum

Di atas adalah proses singkat tentang bagaimana program protokol umum berpartisipasi dalam mengakses API pertukaran (FMZ yang tidak dikemas). Proses ini hanya menjelaskan cara memanggil alat debugging platform (FMZ).exchange.GetTicker()Proses fungsi. Berikutnya, detail interaksi semua fungsi API platform akan dijelaskan secara rinci.

Platform ini merangkum fungsi-fungsi umum dari berbagai bursa dan menyatukannya ke dalam fungsi tertentu, seperti fungsi GetTicker, yang meminta informasi pasar terkini dari suatu produk tertentu. Ini pada dasarnya adalah API yang dimiliki semua bursa. Jadi ketika antarmuka API yang dienkapsulasi platform diakses dalam contoh strategi, kustodian akan mengirimkan permintaan ke plug-in “Protokol Universal” (disebutkan di atas):

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

Saat memanggil fungsi API yang dienkapsulasi platform penemu yang berbeda dalam strategi (seperti GetTicker), format permintaan yang dikirim oleh kustodian ke protokol umum juga akan berbeda. Data (JSON) di badan hanya berbeda dimethodDanparams. Saat merancang protokol umum,methodAnda dapat melakukan operasi spesifik berdasarkan konten. Berikut ini adalah skenario permintaan-respons untuk semua antarmuka.

Pertukaran Spot

Misalnya, pasangan perdagangan saat ini adalah:ETH_USDTSaya tidak akan membahas rinciannya nanti. Data yang diharapkan kustodian agar ditanggapi oleh protokol umum sebagian besar ditulis dalam bidang data, dan bidang mentah juga dapat ditambahkan untuk merekam data asli dari antarmuka pertukaran.

  • GetTicker

    • bidang metode: “ticker”
    • bidang params:
    {"symbol":"ETH_USDT"}
    
    • Data yang diharapkan host dalam respons protokol umum:
    {
        "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

    • bidang metode: “kedalaman”
    • bidang params:
    {"limit":"30","symbol":"ETH_USDT"}
    
    • Data yang diharapkan host dalam respons protokol umum:
    {
        "data" : {
            "time" : 1500793319499,
            "asks" : [
                [1000, 0.5], [1001, 0.23], [1004, 2.1]
                // ... 
            ],
            "bids" : [
                [999, 0.25], [998, 0.8], [995, 1.4]
                // ... 
            ]
        }
    }
    
  • GetTrades

    • bidang metode: “perdagangan”
    • bidang params:
    {"symbol":"eth_usdt"}
    
    • Data yang diharapkan host dalam respons protokol umum:
    { 
        "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

    • bidang metode: “catatan”
    • bidang params:
    {
        "limit":"500",
        "period":"60",          // 60分钟
        "symbol":"ETH_USDT"
    }
    
    • Data yang diharapkan host dalam respons protokol umum:
    {
        "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 Akan dilaksanakan

    • bidang metode: “”
    • bidang params:
    {}
    
    • Data yang diharapkan host dalam respons protokol umum:
    {}
    
  • GetTickers Akan dilaksanakan

    • bidang metode: “”
    • bidang params:
    {}
    
    • Data yang diharapkan host dalam respons protokol umum:
    {}
    
  • GetAccount

    • bidang metode: “akun”
    • bidang params:
    {}
    
    • Data yang diharapkan host dalam respons protokol umum:
    {
        "data": [
            {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
            {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
            // ...
        ]
    }
    
  • GetAssets

    • bidang metode: “aset”
    • bidang params:
    {}
    
    • Data yang diharapkan host dalam respons protokol umum:
    {
        "data": [
            {"currency": "TUSD", "free": "3000", "frozen": "0"},
            {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
            // ...
        ]
    }
    
  • CreateOrder / Buy / Sell

    • bidang metode: “perdagangan”
    • bidang params:
    {"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
    
    • Data yang diharapkan host dalam respons protokol umum:
    {
        "data": {
            "id": "BTC-USDT,123456"
        }
    }
    
  • GetOrders

    • bidang metode: “pesanan”
    • bidang params:
    {"symbol":"ETH_USDT"}
    
    • Data yang diharapkan host dalam respons protokol umum:
    {
        "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

    • bidang metode: “pesanan”
    • bidang params:
    {
        "id":"ETH-USDT,123456",       // 策略中调用:exchange.GetOrder("ETH-USDT,123456")
        "symbol":"ETH_USDT"
    }
    
    • Data yang diharapkan host dalam respons protokol umum:
    { 
        "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

    • bidang metode: “historyorders”
    • bidang params:
    {"limit":0,"since":0,"symbol":"ETH_USDT"}
    
    • Data yang diharapkan host dalam respons protokol umum:
    {
        "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

    • bidang metode: “batal”
    • bidang params:
    {"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
    
    • Data yang diharapkan host dalam respons protokol umum:
    {
        "data": true    // 只要该JSON中没有error字段,都默认为撤单成功
    }
    
  • IO

Fungsi exchange.IO digunakan untuk mengakses langsung antarmuka pertukaran. Misalnya, kami menggunakanGET /api/v5/trade/orders-pending, 参数:instType=SPOT,instId=ETH-USDTMisalnya.

  // 策略实例中调用
  exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")
  • bidang metode:"__api_/api/v5/trade/orders-pending"Bidang metode dimulai dengan _api, yang menunjukkan bahwa ini dipicu oleh panggilan fungsi exchange.IO dalam contoh strategi.

  • bidang params:

    {"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
    
  • Data yang diharapkan host dalam respons protokol umum:

    {
        "data": {"code": "0", "data": [], "msg": ""}    // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
    }
    
  • lainnya Fungsi API Platform Inventor lainnya yang digunakan dalam contoh strategi, seperti: exchange.Go()exchange.GetRawJSON()Fungsi seperti ini tidak perlu dienkapsulasi, dan metode pemanggil serta fungsionalitasnya tetap tidak berubah.

Bursa Berjangka

Selain mendukung semua fungsi bursa spot, bursa berjangka juga memiliki beberapa fungsi API yang unik untuk bursa berjangka.

Akan dilaksanakan

  • GetPositions
  • SetMarginLevel
  • GetFundings

Versi Python dari contoh protokol umum

Protokol umum REST - akses ke antarmuka REST API bursa OKX dan enkapsulasinya sebagai objek bursa spot. Menerapkan antarmuka umum untuk enkapsulasi data permintaan dan respons. Menerapkan tanda tangan antarmuka pribadi, enkapsulasi data permintaan dan respons. Contoh ini terutama untuk pengujian dan pembelajaran. Antarmuka lainnya menggunakan data simulasi untuk langsung merespons host untuk pengujian.

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