Sumber daya yang dimuat... Pemuatan...

FMZ Quant Trading Platform Panduan Akses Protokol Khusus

Penulis:FMZ~Lydia, Dibuat: 2024-11-08 16:37:25, Diperbarui: 2024-11-19 13:31:01

img

Kami FMZ Quant Trading Platform mendukung banyak pertukaran cryptocurrency dan merangkum pertukaran arus utama di pasar. Namun, masih ada banyak pertukaran yang tidak merangkum. Bagi pengguna yang perlu menggunakan pertukaran ini, mereka dapat mengaksesnya melalui FMZ Quant Custom Protocol.RESTprotokol atauFIXprotokol juga dapat diakses.

Artikel ini akan membahasRESTakses protokol sebagai contoh untuk menjelaskan bagaimana menggunakan protokol kustom dari FMZ Quant Trading Platform untuk mengkapsul dan mengakses API dari pertukaran OKX. Kecuali ditentukan lain, artikel ini mengacu pada protokol kustom REST.

  • Aliran kerja dari protokol kustom adalah: Proses permintaan: Instansi strategi berjalan di docker -> Program protokol khusus -> Exchange API Proses respons: Exchange API -> Program protokol khusus -> Instansi strategi yang berjalan di docker

1. Mengkonfigurasi Exchange

Halaman untuk mengkonfigurasi pertukaran di Platform Perdagangan Kuantum FMZ:

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

img

  • Pilih protokol: Pilih Protocol Custom.
  • Alamat layanan: Program protokol kustom pada dasarnya adalah layanan RPC. Oleh karena itu, perlu untuk menentukan alamat layanan dan port dengan jelas ketika mengkonfigurasi pertukaran. Dengan cara ini, docker tahu di mana untuk mengakses program protokol kustom selama prosesStrategy instance running on the docker -> Custom protocol programAku tidak tahu. Misalnya:http://127.0.0.1:6666/OKX, program protokol kustom dan docker biasanya dijalankan pada perangkat yang sama (server), sehingga alamat layanan ditulis sebagai mesin lokal (localhost), dan port dapat digunakan sebagai port yang tidak diduduki oleh sistem.
  • Kunci Akses: Informasi konfigurasi pertukaran yang diteruskan selama prosesStrategy instance running on the docker -> Custom protocol program.
  • Kunci Rahasia: Informasi konfigurasi pertukaran yang diteruskan selama prosesStrategy instance running on the docker -> Custom protocol program.
  • Tag: Tag objek pertukaran di Platform Perdagangan Kuantum FMZ, digunakan untuk mengidentifikasi objek pertukaran tertentu.

Screenshot dari konfigurasi OKX plug-in yang diungkapkan dalam artikel adalah sebagai berikut:

img

OKX pertukaran informasi konfigurasi kunci rahasia:

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

2. Penyebaran Docker dan Program Protokol Khusus (plugin)

    1. Docker Docker harus digunakan untuk menjalankan strategi apa pun di FMZ Quant Trading Platform. Untuk rincian tentang cara menggunakan docker, silakan lihat tutorial platform.
    1. Program Protokol Khusus (plugin) Docker dan protokol kustom biasanya digunakan pada perangkat yang sama. Program protokol (layanan) kustom 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 sebelumnya). Tentu saja, FMZ juga mendukung menjalankan program python, dan Anda juga dapat menjalankan protokol kustom ini sebagai perdagangan langsung untuk menyediakan FMZ Quant Trading Platform dengan dukungan untuk akses API pertukaran yang tidak dikemas. Setelah program protokol khusus berjalan, mulai mendengarkan:http://127.0.0.1:6666. Program protokol kustom dapat memproses jalur tertentu, seperti/OKX.

3. Strategi Instansi Permintaan FMZ API Fungsi

Ketika fungsi API platform (FMZ) dipanggil dalam strategi, program protokol kustom akan menerima permintaan dari docker. Anda juga dapat mengujinya menggunakan alat debugging platform, misalnya:

Halaman alat debugging:

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

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

Menelpon fungsiexchange.GetTicker(), program protokol khusus 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 dikonfigurasi di platform Configure Exchange di atas
  • secret_key: Kunci pertukaran yang dikonfigurasi di platform Configure Exchange di atas
  • metode: terkait dengan antarmuka panggilan dalam strategi, ketika panggilanexchange.GetTicker(), methodadalahticker.
  • nonce: Stempel waktu ketika permintaan terjadi.
  • Parameter: Parameter yang terkait dengan panggilan antarmuka dalam strategi, dalam contoh ini, ketika memanggilexchange.GetTicker(), parameter terkait adalah:{"symbol":"LTC_USDT"}.

4. Akses Program Protokol Khusus ke Interface Exchange

Ketika program protokol kustom menerima permintaan dari docker, 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 kustom dapat mengakses antarmuka pertukaran untuk mendapatkan data yang diperlukan atau melakukan operasi tertentu.

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

  • Antarmuka publik: antarmuka yang tidak memerlukan verifikasi tanda tangan dan diminta langsung dalam program protokol khusus.
  • Private interface: sebuah antarmuka yang membutuhkan verifikasi tanda tangan. tanda tangan perlu diimplementasikan dalam program protokol kustom untuk meminta antarmuka API pertukaran ini.

Program protokol kustom menerima data respons dari antarmuka pertukaran dan memprosesnya lebih lanjut untuk membangun data yang diharapkan oleh docker (digambarkan di bawah ini).GetTicker, GetAccountdan fungsi lain dalam implementasi kelas CustomProtocolOKX dalam contoh protokol kustom Python.

5. Program Protokol Khusus Menanggapi Data ke Docker

Ketika program protokol kustom mengakses antarmuka API pertukaran, melakukan operasi tertentu atau memperoleh data tertentu, ia perlu memberi masukan kembali hasil ke docker.

Data yang dimasukkan kembali ke docker bervariasi sesuai dengan antarmuka yang dipanggil oleh strategi, dan pertama dibagi menjadi dua kategori:

  • Program protokol kustom memanggil antarmuka pertukaran dengan sukses:
{
    "data": null,  // "data" can be of any type 
    "raw": null    // "raw" can be of any type 
}

data: Struktur khusus dari bidang ini terkait denganmethoddalam permintaan yang diterima oleh program protokol khusus, dan digunakan untuk membangun struktur data yang akhirnya dikembalikan oleh fungsi API platform FMZ. Semua antarmuka akan tercantum di bawah ini. raw: Bidang ini dapat digunakan untuk melewati data mentah dari tanggapan antarmuka API pertukaran, seperti struktur Ticker yang dikembalikan olehexchange.GetTicker()Bidang Info dari struktur Ticker mencatat data darirawlapangan dandatabeberapa fungsi API platform tidak membutuhkan data ini.

  • Program protokol khusus gagal memanggil antarmuka pertukaran (kesalahan bisnis, kesalahan jaringan, dll.)
{
    "error": ""    // "error" contains an error message as a string
}

error: informasi kesalahan, yang akan ditampilkan dalam log kesalahan di area log platform (FMZ) live trading, alat debugging dan halaman lainnya.

Menunjukkan data respon protokol khusus 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 singkat dari program protokol kustom yang berpartisipasi dalam akses ke (FMZ tidak dikemas) API pertukaran.exchange.GetTicker()Berikut ini akan menjelaskan rincian interaksi dari semua fungsi API platform secara rinci.

Platform ini merangkum fungsi-fungsi umum dari berbagai pertukaran dan menyatukan mereka ke dalam fungsi tertentu, seperti fungsi GetTicker, yang meminta informasi pasar saat ini dari produk tertentu. Ini adalah API yang pada dasarnya semua pertukaran memiliki. Oleh karena itu, ketika mengakses antarmuka API yang dikemas oleh platform dalam contoh strategi, docker akan mengirim permintaan ke program Custom Protocol plug-in (disebut di atas):

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

Ketika memanggil fungsi API yang dikapsulkan platform FMZ yang berbeda dalam strategi (seperti GetTicker), format permintaan yang dikirim oleh docker ke protokol kustom juga akan berbeda.methoddanparams. Ketika merancang protokol kustom, melakukan operasi tertentu sesuai dengan isi metode. Berikut adalah skenario permintaan-respon untuk semua antarmuka.

Pertukaran Spot

Misalnya, pasangan perdagangan saat ini adalah:ETH_USDTData yang docker mengharapkan protokol kustom untuk menanggapi terutama ditulis dalam bidang data, dan bidang mentah juga dapat ditambahkan untuk merekam data asli antarmuka pertukaran.

  • GetTicker

bidang metode: ticker bidang parameter:

{"symbol":"ETH_USDT"}

Data yang diharapkan oleh docker dalam respon protokol khusus:

{
    "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 metode: kedalaman bidang parameter:

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

Data yang diharapkan oleh docker dalam respon protokol khusus:

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

{"symbol":"eth_usdt"}

Data yang diharapkan oleh docker dalam respon protokol khusus:

{ 
    "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: rekor bidang parameter:

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

Data yang diharapkan oleh docker dalam respon protokol khusus:

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

bidang metode: bidang parameter:

{}

Data yang diharapkan oleh docker dalam respon protokol khusus:

{}
  • GetTickersUntuk dilaksanakan

bidang metode: bidang parameter:

{}

Data yang diharapkan oleh docker dalam respon protokol khusus:

{}
  • DapatkanAkun

bidang metode: account bidang parameter:

{}

Data yang diharapkan oleh docker dalam respon protokol khusus:

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

bidang metode: aset bidang parameter:

{}

Data yang diharapkan oleh docker dalam respon protokol khusus:

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

kolom metode: dagang bidang parameter:

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

Data yang diharapkan oleh docker dalam respon protokol khusus:

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

bidang metode: pesanan bidang parameter:

{"symbol":"ETH_USDT"}

Data yang diharapkan oleh docker dalam respon protokol khusus:

{
    "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: order bidang parameter:

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

Data yang diharapkan oleh docker dalam respon protokol khusus:

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

bidang metode: history orders bidang parameter:

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

Data yang diharapkan oleh docker dalam respon protokol khusus:

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

kolom metode: batal bidang parameter:

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

Data yang diharapkan oleh docker dalam respon protokol khusus:

{
    "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 antarmuka 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")

bidang metode:"__api_/api/v5/trade/orders-pending", bidang metode dimulai dengan _api, menunjukkan bahwa ini dipicu olehexchange.IOpanggilan fungsi dalam contoh strategi. bidang parameter:

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

Data yang diharapkan oleh docker dalam respon protokol khusus:

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

Bursa Berjangka

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

Untuk dilaksanakan

  • DapatkanPosisi
  • SetMarginLevel
  • GetFundings

Contoh protokol khusus dalam versi Python

REST Custom Protocol - Akses ke antarmuka OKX exchange REST API, terkapas sebagai objek spot exchange. Menerapkan permintaan antarmuka publik dan data respon yang terkapas. Mengimplementasikan tanda tangan antarmuka pribadi, permintaan dan respon data encapsulation. Contoh ini terutama untuk pengujian dan pembelajaran. antarmuka lainnya menggunakan data simulasi untuk langsung merespons docker 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'
        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 banyak