Sumber dimuat naik... memuat...

FMZ Quant Trading Platform Panduan Akses Protokol Sesuai

Penulis:FMZ~Lydia, Dicipta: 2024-11-08 16:37:25, Dikemas kini: 2024-11-08 16:38:52

Kami Platform Perdagangan Kuantum FMZ menyokong banyak bursa cryptocurrency dan merangkumi bursa arus perdana di pasaran. Walau bagaimanapun, masih ada banyak bursa yang tidak terkurung. Bagi pengguna yang perlu menggunakan bursa ini, mereka boleh mengaksesnya melalui Protokol Custom FMZ Quant. Bukan hanya terhad kepada bursa cryptocurrency, mana-mana platform yang menyokongRESTprotokol atauFIXProtokol juga boleh diakses.

Artikel ini akan mengambilRESTakses protokol sebagai contoh untuk menerangkan cara menggunakan protokol tersuai Platform Dagangan Kuantum FMZ untuk mengkapsul dan mengakses API pertukaran OKX. Kecuali dinyatakan sebaliknya, artikel ini merujuk kepada protokol tersuai REST.

  • Aliran kerja protokol tersuai adalah: Prosedur permintaan: Instan strategi yang berjalan pada docker -> Program protokol tersuai -> Exchange API Proses tindak balas: Exchange API -> Program protokol tersuai -> Instan strategi yang berjalan di docker

1. Mengatur Pertukaran

Halaman untuk mengkonfigurasi pertukaran di Platform Dagangan Kuantum FMZ:

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

img

  • Pilih protokol: Pilih Protocol Custom.
  • Alamat perkhidmatan: Program protokol tersuai pada dasarnya adalah perkhidmatan RPC. Oleh itu, adalah perlu untuk menentukan alamat perkhidmatan dan port dengan jelas apabila mengkonfigurasi pertukaran.Strategy instance running on the docker -> Custom protocol program. Contohnya:http://127.0.0.1:6666/OKX, program protokol tersuai dan docker biasanya dijalankan pada peranti yang sama (server), jadi alamat perkhidmatan ditulis sebagai mesin tempatan (localhost), dan port boleh digunakan sebagai port yang tidak diduduki oleh sistem.
  • Kunci Akses: Maklumat konfigurasi pertukaran yang diteruskan semasa prosesStrategy instance running on the docker -> Custom protocol program.
  • Kunci Rahsia: Maklumat konfigurasi pertukaran yang diteruskan semasa prosesStrategy instance running on the docker -> Custom protocol program.
  • Tag: Tag objek pertukaran di Platform Dagangan Kuantum FMZ, digunakan untuk mengenal pasti objek pertukaran tertentu.

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

img

OKX pertukaran maklumat konfigurasi kunci rahsia:

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

2. Pengerahan Docker dan Program Protokol Sesuai (plugin)

    1. Docker Docker mesti digunakan untuk menjalankan mana-mana strategi di Platform Dagangan Kuantum FMZ. Untuk butiran mengenai cara menggunakan docker, sila rujuk tutorial platform.
    1. Program Protokol tersuai (plugin) Docker dan protokol tersuai biasanya digunakan pada peranti yang sama. Program protokol (perkhidmatan) tersuai boleh ditulis dalam bahasa apa pun. Artikel ini ditulis dalam Python3. Sama seperti menjalankan mana-mana program Python, anda boleh melaksanakannya secara langsung (membuat pelbagai konfigurasi persekitaran Python terlebih dahulu). Sudah tentu, FMZ juga menyokong menjalankan program python, dan anda juga boleh menjalankan protokol tersuai ini sebagai perdagangan langsung untuk menyediakan Platform Perdagangan Kuantum FMZ dengan sokongan untuk akses API pertukaran yang tidak dikemas. Selepas program protokol tersuai berjalan, ia mula mendengar:http://127.0.0.1:6666. Program protokol tersuai boleh memproses laluan tertentu, seperti/OKX.

3. Strategi contoh Permintaan FMZ API Fungsi

Apabila fungsi API platform (FMZ) dipanggil dalam strategi, program protokol tersuai akan menerima permintaan dari docker.

Halaman alat penyempurnaan:

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

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

Menghantar fungsiexchange.GetTicker(), program protokol tersuai 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 Configure Exchange di atas
  • secret_key: Kunci pertukaran yang dikonfigurasikan dalam platform Configure Exchange di atas
  • kaedah: berkaitan dengan antara muka panggilan dalam strategi, apabila memanggilexchange.GetTicker(), methodadalahticker.
  • nonce: Stempel masa apabila permintaan berlaku.
  • parameter: Parameter yang berkaitan dengan panggilan antara muka dalam strategi, dalam contoh ini, apabila memanggilexchange.GetTicker(), parameter yang berkaitan adalah:{"symbol":"LTC_USDT"}.

4. Akses Program Protokol tersuai kepada Antara muka Pertukaran

Apabila program protokol tersuai menerima permintaan dari docker, ia boleh mendapatkan maklumat seperti fungsi API platform (termasuk maklumat parameter) yang diminta oleh strategi, kunci pertukaran, dll berdasarkan maklumat yang dibawa dalam permintaan.

Berdasarkan maklumat ini, program protokol tersuai 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 tersuai.
  • Antara muka peribadi: antara muka yang memerlukan pengesahan tandatangan. Tandatangan perlu dilaksanakan dalam program protokol tersuai untuk meminta antara muka API pertukaran ini.

Program protokol tersuai menerima data tindak balas dari antara muka pertukaran dan memprosesnya lagi untuk membina data yang diharapkan oleh docker (digambarkan di bawah).GetTicker, GetAccountdan fungsi lain dalam pelaksanaan kelas CustomProtocolOKX dalam contoh protokol adat Python.

5. Program Protokol Sesuai Menjawab Data ke Docker

Apabila program protokol tersuai mengakses antara muka API pertukaran, melakukan operasi tertentu atau memperoleh data tertentu, ia perlu memberi masukan semula hasil kepada docker.

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

  • Program protokol tersuai memanggil antara muka pertukaran dengan berjaya:
{
    "data": null,  // "data" can be of any type 
    "raw": null    // "raw" can be of any type 
}

data: Struktur khusus bidang ini berkaitan denganmethoddalam permintaan yang diterima oleh program protokol tersuai, dan digunakan untuk membina struktur data akhirnya dikembalikan oleh fungsi API platform FMZ. Semua antara muka akan disenaraikan di bawah. mentah: medan ini boleh digunakan untuk lulus dalam data mentah tindak balas antara muka API pertukaran, seperti struktur Ticker dikembalikan olehexchange.GetTicker()medan Info struktur Ticker merakam datarawlapangan dandatamedan; beberapa fungsi API platform tidak memerlukan data ini.

  • Program protokol tersuai gagal memanggil antara muka pertukaran (kesalahan perniagaan, kesalahan rangkaian, dll.)
{
    "error": ""    // "error" contains an error message as a string
}

kesalahan: maklumat kesilapan, yang akan dipaparkan dalam log ralat di kawasan log platform (FMZ) perdagangan langsung, alat pecahannya dan halaman lain.

Menunjukkan data tindak balas protokol tersuai yang diterima oleh program strategi:

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

img

6. Perjanjian Struktur Data dalam Protokol Custom

Di atas adalah proses ringkas program protokol tersuai yang mengambil bahagian dalam akses kepada (FMZ tidak dibungkus) pertukaran API. Proses ini hanya menerangkan proses apabila memanggilexchange.GetTicker()Berikut akan menerangkan butiran interaksi semua fungsi API platform secara terperinci.

Platform ini merangkumi fungsi umum pelbagai pertukaran dan menyatukan mereka ke dalam fungsi tertentu, seperti fungsi GetTicker, yang meminta maklumat pasaran semasa produk tertentu. Ini adalah API yang pada dasarnya semua pertukaran mempunyai. Oleh itu, apabila mengakses antara muka API yang dikemas dalam platform dalam contoh strategi, docker akan menghantar permintaan ke program pemalam Custom Protocol (diperhatikan 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 yang dikapsulkan platform FMZ yang berbeza dalam strategi (seperti GetTicker), format permintaan yang dihantar oleh docker ke protokol tersuai juga akan berbeza.methoddanparams. Apabila merancang protokol tersuai, menjalankan operasi tertentu mengikut kandungan kaedah. Berikut adalah senario permintaan-jawapan untuk semua antara muka.

Pertukaran Spot

Sebagai contoh, pasangan dagangan semasa adalah:ETH_USDTData yang docker menjangkakan protokol tersuai untuk bertindak balas adalah terutamanya ditulis dalam medan data, dan medan mentah juga boleh ditambah untuk merakam data asal antara muka pertukaran.

  • GetTicker

medan kaedah: ticker medan param:

{"symbol":"ETH_USDT"}

Data yang docker mengharapkan dalam tindak balas protokol tersuai:

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

medan kaedah: kedalaman medan param:

{"limit":"30","symbol":"ETH_USDT"}

Data yang docker mengharapkan dalam tindak balas protokol tersuai:

{
    "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 param:

{"symbol":"eth_usdt"}

Data yang docker mengharapkan dalam tindak balas protokol tersuai:

{ 
    "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 param:

{
    "limit":"500",
    "period":"60",          // 60 minutes
    "symbol":"ETH_USDT"
}

Data yang docker mengharapkan dalam tindak balas protokol tersuai:

{
    "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],
            // ...
    ]
}
  • GetMarketsUntuk dilaksanakan

medan kaedah: medan param:

{}

Data yang docker mengharapkan dalam tindak balas protokol tersuai:

{}
  • GetTickersUntuk dilaksanakan

medan kaedah: medan param:

{}

Data yang docker mengharapkan dalam tindak balas protokol tersuai:

{}
  • DapatkanAkaun

medan kaedah: akaun medan param:

{}

Data yang docker mengharapkan dalam tindak balas protokol tersuai:

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

medan kaedah: aset medan param:

{}

Data yang docker mengharapkan dalam tindak balas protokol tersuai:

{
    "data": [
        {"currency": "TUSD", "free": "3000", "frozen": "0"},
        {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
        // ...
    ]
}
  • BuatOrder / Beli / Jual

medan kaedah: dagang medan param:

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

Data yang docker mengharapkan dalam tindak balas protokol tersuai:

{
    "data": {
        "id": "BTC-USDT,123456"
    }
}
  • GetOrders

medan kaedah: pesanan medan param:

{"symbol":"ETH_USDT"}

Data yang docker mengharapkan dalam tindak balas protokol tersuai:

{
    "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: order medan param:

{
    "id":"ETH-USDT,123456",       // Calling in the strategy: exchange.GetOrder("ETH-USDT,123456")
    "symbol":"ETH_USDT"
}

Data yang docker mengharapkan dalam tindak balas protokol tersuai:

{ 
    "data": {
        "id": "ETH-USDT,123456",
        "symbol": "ETH_USDT"
        "amount": 0.15,
        "price": 1002,
        "status": "pending",    // "pending", "pre-submitted", "submitting", "submitted", "partial-filled", "filled", "closed", "finished", "partial-canceled", "canceled"
        "deal_amount": 0,
        "type": "buy",          // "buy"、"sell"
        "avg_price": 0,         // If the exchange does not provide it, it can be assigned a value of 0 during processing.
    }
}
  • GetHistoryOrders

medan kaedah: history orders medan param:

{"limit":0,"since":0,"symbol":"ETH_USDT"}

Data yang docker mengharapkan dalam tindak balas protokol tersuai:

{
    "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"
        }, 
        // ...
    ]
}
  • Batalkan Perintah

medan kaedah: batalkan medan param:

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

Data yang docker mengharapkan dalam tindak balas protokol tersuai:

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

Peraturanexchange.IOfungsi digunakan untuk mengakses antara muka pertukaran secara langsung.GET /api/v5/trade/orders-pending, parameters: instType=SPOT, instId=ETH-USDTSebagai contoh.

// Called in the strategy instance
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 dipicu olehexchange.IOpanggilan fungsi dalam contoh strategi. medan param:

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

Data yang docker mengharapkan dalam tindak balas protokol tersuai:

{
    "data": {"code": "0", "data": [], "msg": ""}    // The data attribute value is the data of the exchange API: GET /api/v5/trade/orders-pending response
}
  • Lain-lain Fungsi API platform FMZ lain yang digunakan dalam contoh strategi, seperti:exchange.Go(), exchange.GetRawJSON()dan fungsi lain tidak perlu dikapsulkan, dan kaedah panggilan dan fungsi kekal tidak berubah.

Pertukaran niaga hadapan

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

Untuk dilaksanakan

  • DapatkanPosisi
  • SetMarginLevel
  • GetFundings

Contoh Protokol Sesuai dalam Versi Python

REST Custom Protocol - Akses kepada OKX pertukaran REST API antara muka, dilampirkan sebagai objek pertukaran spot. Melaksanakan permintaan antarmuka awam dan data tindak balas. Melaksanakan tandatangan antara muka persendirian, permintaan dan tindak balas data encapsulation. Contoh ini adalah terutamanya untuk ujian dan pembelajaran. antara muka lain menggunakan data yang disimulasikan untuk bertindak balas secara langsung kepada docker 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'
        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()

Lebih lanjut