avatar of 发明者量化-小小梦 发明者量化-小小梦
fokus pada mesej peribadi
4
fokus pada
1271
Pengikut

Panduan Akses Protokol Umum Platform Perdagangan Kuantitatif Pencipta

Dicipta dalam: 2024-10-29 14:37:56, dikemas kini pada: 2024-11-12 21:58:55
comments   0
hits   865

[TOC]

Panduan Akses Protokol Umum Platform Perdagangan Kuantitatif Pencipta

Platform Dagangan Kuantitatif Inventor menyokong banyak pertukaran mata wang kripto dan merangkumi pertukaran arus perdana di pasaran. Walau bagaimanapun, masih terdapat banyak pertukaran yang tidak dibungkus Bagi pengguna yang perlu menggunakan pertukaran ini, mereka boleh mengaksesnya melalui protokol universal yang dikira oleh pencipta. Tidak terhad kepada pertukaran mata wang kripto, mana-manaRESTPerjanjian atauFIXPlatform perjanjian juga boleh diakses.

Artikel ini akanRESTMengambil akses protokol sebagai contoh, ia menerangkan cara menggunakan protokol umum Platform Dagangan Kuantitatif Pencipta untuk merangkum dan mengakses API OKX Exchange. Melainkan dinyatakan sebaliknya, artikel ini merujuk kepada protokol umum REST.

  • Aliran kerja protokol umum ialah: Proses permintaan: Contoh strategi berjalan pada penjaga -> Program protokol umum -> API Pertukaran Proses respons: Exchange API -> Program protokol umum -> Contoh strategi berjalan pada penjaga

1. Konfigurasikan pertukaran

Halaman untuk mengkonfigurasi pertukaran pada Platform Dagangan Kuantitatif Pencipta:

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

Panduan Akses Protokol Umum Platform Perdagangan Kuantitatif Pencipta

  • Pilih protokol: Pilih “Protokol Umum”.
  • Alamat perkhidmatan: Program protokol umum pada asasnya ialah perkhidmatan RPC Oleh itu, adalah perlu untuk menyatakan dengan jelas alamat perkhidmatan dan port semasa mengkonfigurasi pertukaran. Jadi dalam托管者上运行的策略实例 -> 通用协议程序Semasa proses itu, hos mengetahui tempat untuk mengakses program protokol biasa. Contohnya:http://127.0.0.1:6666/OKXBiasanya, program protokol biasa dan hos dijalankan pada peranti yang sama (pelayan), jadi alamat perkhidmatan ditulis sebagai mesin tempatan (localhost), dan port boleh menjadi port yang tidak diduduki oleh sistem.
  • Access Key: 托管者上运行的策略实例 -> 通用协议程序Maklumat konfigurasi pertukaran diluluskan semasa proses.
  • Secret Key: 托管者上运行的策略实例 -> 通用协议程序Maklumat konfigurasi pertukaran diluluskan semasa proses.
  • Label: Label objek pertukaran pada Platform Dagangan Kuantitatif Pencipta digunakan untuk mengenal pasti objek pertukaran tertentu.

Tangkapan skrin konfigurasi pemalam OKX yang didedahkan dalam artikel adalah seperti berikut:

Panduan Akses Protokol Umum Platform Perdagangan Kuantitatif Pencipta

OKX bertukar maklumat konfigurasi kunci rahsia:

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

2. Penggunaan penjaga dan program protokol universal (plugin)

    1. Hos Untuk menjalankan sebarang strategi masa nyata pada Platform Dagangan Kuantitatif Pencipta, anda mesti menggunakan kustodian Untuk penempatan khusus penjaga, sila rujuk tutorial platform, yang tidak akan diulang di sini.
    1. Program protokol umum (plugin) Hos dan protokol universal biasanya digunakan pada peranti yang sama Program protokol (perkhidmatan) universal boleh ditulis dalam mana-mana bahasa Artikel ini ditulis dalam Python3. Sama seperti menjalankan sebarang program Python, anda boleh melaksanakannya secara langsung (membuat pelbagai konfigurasi persekitaran Python terlebih dahulu). Sudah tentu, FMZ juga menyokong menjalankan program Python, dan protokol umum ini boleh dijalankan sebagai cakera sebenar untuk menyediakan platform dagangan kuantitatif pencipta dengan sokongan untuk akses API pertukaran yang tidak dibungkus. Selepas program protokol umum berjalan, mulakan pemantauan:http://127.0.0.1:6666,Dalam program protokol umum, laluan khusus boleh ditentukan, sebagai contoh/OKXuntuk diproses.

3. Contoh strategi meminta fungsi API FMZ

Apabila fungsi API platform (FMZ) dipanggil dalam strategi, Program Protokol Universal menerima permintaan daripada penjaga. Anda juga boleh menguji menggunakan alat penyahpepijatan platform, contohnya:

Halaman alat penyahpepijatan:

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

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

Panggilexchange.GetTicker()Berfungsi, 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 dalam platform “Konfigurasikan Exchange” di atas
  • secret_key: Kunci pertukaran yang dikonfigurasikan dalam platform “Konfigurasikan Pertukaran” di atas
  • kaedah: berkaitan dengan antara muka panggilan dalam strategi, memanggilexchange.GetTicker()jam,methodiaituticker
  • nonce: Cap masa apabila permintaan berlaku.
  • params: Parameter yang berkaitan dengan panggilan antara muka dalam dasar.exchange.GetTicker()Apabila memanggil, parameter yang berkaitan ialah:{"symbol":"LTC_USDT"}

4. Akses program protokol umum ke antara muka pertukaran

Apabila program protokol umum menerima permintaan daripada penjaga, ia boleh mendapatkan maklumat seperti fungsi API platform (termasuk maklumat parameter) yang diminta oleh strategi, kunci pertukaran, dsb. berdasarkan maklumat yang dibawa dalam permintaan.

Berdasarkan maklumat ini, program protokol umum boleh mengakses antara muka pertukaran untuk mendapatkan data yang diperlukan atau melakukan operasi tertentu.

Biasanya antara muka pertukaran mempunyai kaedah seperti GET/POST/PUT/DELETE, yang dibahagikan kepada antara muka awam dan antara muka peribadi.

  • Antara muka awam: antara muka yang tidak memerlukan pengesahan tandatangan dan diminta secara langsung dalam program protokol umum.
  • Antara muka peribadi: antara muka yang memerlukan pengesahan tandatangan. Tandatangan perlu dilaksanakan dalam program protokol umum untuk meminta antara muka API pertukaran ini.

Program protokol umum menerima data tindak balas antara muka pertukaran, memprosesnya lagi dan membinanya ke dalam data yang diharapkan oleh penjaga (diterangkan di bawah). Rujuk pertukaran tempat OKX, pelaksanaan kelas CustomProtocolOKX dalam contoh protokol umum PythonGetTickerGetAccountDan fungsi lain.

5. Program protokol am bertindak balas data kepada penjaga

Apabila program protokol umum mengakses antara muka API pertukaran, menjalankan operasi tertentu atau memperoleh data tertentu, ia perlu memberi suapan kembali keputusan kepada penjaga.

Data yang disalurkan kembali kepada penjaga berbeza-beza mengikut antara muka yang dipanggil oleh strategi, dan mula-mula dibahagikan kepada dua kategori:

  • Program protokol umum berjaya memanggil antara muka pertukaran:
  {
      "data": null,  // "data" can be of any type 
      "raw": null    // "raw" can be of any type 
  }
  • data: Struktur khusus medan ini adalah sama seperti dalam permintaan yang diterima oleh program protokol umum.methodBerikut ialah senarai semua antara muka yang digunakan untuk membina struktur data yang dikembalikan oleh fungsi API platform FMZ.

  • Raw: Medan ini boleh digunakan untuk menghantar data mentah respons API pertukaran, contohnyaexchange.GetTicker()Struktur Ticker yang dikembalikan oleh fungsi mempunyai maklumat berikut yang direkodkan dalam medan Maklumat struktur Ticker:rawPadang dandataData medan; beberapa fungsi API platform tidak memerlukan data ini.

  • Program protokol umum gagal memanggil antara muka pertukaran (ralat perniagaan, ralat rangkaian, dll.)

  {
      "error": ""    // "error" contains an error message as a string
  }
  • ralat: maklumat ralat, yang akan dipaparkan dalam log ralat di kawasan log cakera sebenar platform (FMZ), alat penyahpepijatan dan halaman lain.

Menunjukkan data tindak balas protokol umum yang diterima oleh program dasar:

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

Panduan Akses Protokol Umum Platform Perdagangan Kuantitatif Pencipta

6. Perjanjian struktur data dalam protokol umum

Di atas ialah proses ringkas tentang cara program protokol umum mengambil bahagian dalam mengakses API pertukaran (FMZ tidak dibungkus) Proses ini hanya menerangkan cara memanggil alat penyahpepijatan platform (FMZ).exchange.GetTicker()Proses fungsi. Seterusnya, butiran interaksi semua fungsi API platform akan diterangkan secara terperinci.

Platform ini merangkum fungsi umum pelbagai pertukaran dan menyatukannya ke dalam fungsi tertentu, seperti fungsi GetTicker, yang meminta maklumat pasaran semasa bagi produk tertentu Ini pada dasarnya adalah API yang dimiliki oleh semua bursa. Oleh itu, apabila antara muka API terkapsul platform diakses dalam contoh strategi, penjaga akan menghantar permintaan kepada pemalam “Protokol Universal” (disebutkan di atas):

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

Apabila memanggil fungsi API terkapsul platform pencipta yang berbeza dalam strategi (seperti GetTicker), format permintaan yang dihantar oleh penjaga kepada protokol umum juga akan berbeza. Data (JSON) dalam badan berbeza hanya dalammethoddanparams. Apabila mereka bentuk protokol umum,methodAnda boleh melakukan operasi khusus berdasarkan kandungan. Berikut ialah senario permintaan-tindak balas untuk semua antara muka.

Pertukaran Spot

Sebagai contoh, pasangan dagangan semasa ialah:ETH_USDT, saya tidak akan menjelaskan secara terperinci kemudian. Data yang penjaga menjangkakan protokol umum bertindak balas terutamanya ditulis dalam medan data, dan medan mentah juga boleh ditambah untuk merekodkan data asal antara muka pertukaran.

  • GetTicker

    • medan kaedah: “ticker”
    • medan params:
    {"symbol":"ETH_USDT"}
    
    • Data yang diharapkan oleh hos dalam tindak balas 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

    • medan kaedah: “kedalaman”
    • medan params:
    {"limit":"30","symbol":"ETH_USDT"}
    
    • Data yang diharapkan oleh hos dalam tindak balas 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

    • medan kaedah: “perdagangan”
    • medan params:
    {"symbol":"eth_usdt"}
    
    • Data yang diharapkan oleh hos dalam tindak balas 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

    • medan kaedah: “rekod”
    • medan params:
    {
        "limit":"500",
        "period":"60",          // 60分钟
        "symbol":"ETH_USDT"
    }
    
    • Data yang diharapkan oleh hos dalam tindak balas 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 Untuk dilaksanakan

    • medan kaedah: “”
    • medan params:
    {}
    
    • Data yang diharapkan oleh hos dalam tindak balas protokol umum:
    {}
    
  • GetTickers Untuk dilaksanakan

    • medan kaedah: “”
    • medan params:
    {}
    
    • Data yang diharapkan oleh hos dalam tindak balas protokol umum:
    {}
    
  • GetAccount

    • medan kaedah: “akaun”
    • medan params:
    {}
    
    • Data yang diharapkan oleh hos dalam tindak balas protokol umum:
    {
        "data": [
            {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
            {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
            // ...
        ]
    }
    
  • GetAssets

    • medan kaedah: “aset”
    • medan params:
    {}
    
    • Data yang diharapkan oleh hos dalam tindak balas protokol umum:
    {
        "data": [
            {"currency": "TUSD", "free": "3000", "frozen": "0"},
            {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
            // ...
        ]
    }
    
  • CreateOrder / Buy / Sell

    • medan kaedah: “perdagangan”
    • medan params:
    {"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
    
    • Data yang diharapkan oleh hos dalam tindak balas protokol umum:
    {
        "data": {
            "id": "BTC-USDT,123456"
        }
    }
    
  • GetOrders

    • medan kaedah: “pesanan”
    • medan params:
    {"symbol":"ETH_USDT"}
    
    • Data yang diharapkan oleh hos dalam tindak balas 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

    • medan kaedah: “pesanan”
    • medan params:
    {
        "id":"ETH-USDT,123456",       // 策略中调用:exchange.GetOrder("ETH-USDT,123456")
        "symbol":"ETH_USDT"
    }
    
    • Data yang diharapkan oleh hos dalam tindak balas 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

    • medan kaedah: “historyorders”
    • medan params:
    {"limit":0,"since":0,"symbol":"ETH_USDT"}
    
    • Data yang diharapkan oleh hos dalam tindak balas 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

    • medan kaedah: “batalkan”
    • medan params:
    {"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
    
    • Data yang diharapkan oleh hos dalam tindak balas protokol umum:
    {
        "data": true    // 只要该JSON中没有error字段,都默认为撤单成功
    }
    
  • IO

Fungsi exchange.IO digunakan untuk mengakses terus antara muka pertukaran Sebagai contoh, kita gunakanGET /api/v5/trade/orders-pending, 参数:instType=SPOT,instId=ETH-USDTContohnya.

  // 策略实例中调用
  exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")
  • medan kaedah:"__api_/api/v5/trade/orders-pending"Medan kaedah bermula dengan _api, menunjukkan bahawa ini dicetuskan oleh panggilan fungsi pertukaran.IO dalam contoh strategi.

  • medan params:

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

    {
        "data": {"code": "0", "data": [], "msg": ""}    // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
    }
    
  • lain Fungsi API Platform Pencipta lain yang digunakan dalam contoh strategi, seperti: exchange.Go()exchange.GetRawJSON()Fungsi seperti ini tidak perlu dikapsulkan, dan kaedah dan fungsi panggilan kekal tidak berubah.

Bursa Niaga Hadapan

Selain menyokong semua fungsi pertukaran spot, bursa niaga hadapan juga mempunyai beberapa fungsi API yang unik kepada bursa niaga hadapan.

Untuk dilaksanakan

  • GetPositions
  • SetMarginLevel
  • GetFundings

Versi Python contoh protokol umum

Protokol umum REST - akses kepada antara muka API REST pertukaran OKX dan merangkumnya sebagai objek pertukaran spot. Melaksanakan antara muka biasa untuk enkapsulasi data permintaan dan tindak balas. Melaksanakan tandatangan antara muka peribadi, enkapsulasi data permintaan dan tindak balas. Contoh ini adalah terutamanya untuk ujian dan pembelajaran Antara muka lain menggunakan data simulasi untuk bertindak balas terus kepada hos untuk ujian.

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