Sumber daya yang dimuat... Pemuatan...

Inventor Quantitative Trading Platforms General Protocol Access Guide (Penggunaan Protokol Umum untuk Pemasaran Kuantitatif)

Penulis:Penemu Kuantitas - Mimpi Kecil, Dibuat: 2024-10-29 14:37:56, Diperbarui: 2024-11-12 21:58:55

[TOC]

img

Platform perdagangan Kuantitatif Penemu mendukung banyak pertukaran cryptocurrency, dan mencakup pertukaran utama di pasar. Namun, masih ada banyak pertukaran yang belum dikemas, yang dapat diakses oleh pengguna yang membutuhkan akses ke mereka melalui protokol umum Kuantitatif Penemu. Tidak terbatas pada pertukaran cryptocurrency, tidak ada dukunganRESTperjanjian atauFIXPlatform protokol juga dapat diakses.

Artikel ini akan membahasRESTSebagai contoh, akses protokol, menjelaskan bagaimana menggunakan protokol umum inventor untuk menampung dan mengakses API OKX.

  • Proses kerja protokol umum adalah sebagai berikut: Proses permintaan: Contoh kebijakan yang berjalan pada administrator -> General Protocol -> API bursa Proses respon: API Exchange -> Program Protokol Umum -> Contoh kebijakan yang berjalan pada administrator

1. Mengkonfigurasi Bursa

Di halaman ini, para penemu mengkonfigurasi bursa di platform perdagangan kuantitatif:

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

img

  • Pilih protokol: Pilih "Protokol Umum".
  • Alamat layanan: General Protocol Procedure pada dasarnya adalah layanan RPC Oleh karena itu, Anda harus menentukan alamat layanan, port, dan lain-lain secara jelas saat mengkonfigurasi bursa.托管者上运行的策略实例 -> 通用协议程序Pada saat proses, administrator tahu di mana untuk mengakses protokol umum. Misalnya:http://127.0.0.1:6666/OKXBiasanya, program protokol umum dijalankan pada perangkat yang sama dengan host (server), sehingga alamat layanan ditulis pada localhost (host lokal) dan port digunakan pada sistem yang tidak ditempati.
  • Kunci Akses:托管者上运行的策略实例 -> 通用协议程序Dalam prosesnya, informasi konfigurasi bursa yang dikirimkan.
  • Kunci Rahasia:托管者上运行的策略实例 -> 通用协议程序Dalam prosesnya, informasi konfigurasi bursa yang dikirimkan.
  • Tag: pembicaraan Label objek bursa di platform perdagangan kuantitatif penemu, yang digunakan untuk mengidentifikasi objek bursa tertentu.

Di bawah ini adalah screenshot dari konfigurasi OKX plugin yang diungkapkan dalam artikel:

img

OKX memiliki beberapa fitur yang dapat Anda gunakan.

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

2. Mengimplementasikan host dan plugin

  • 1. Pengelola Setiap strategi yang dijalankan pada platform perdagangan kuantitatif penemu harus menerapkan administrator, yang dapat dilihat dalam tutorial platform, yang tidak akan dibahas di sini.

  • 2, Program Protokol Umum (plugin) Host dan protokol umum biasanya terpasang pada perangkat yang sama, program protokol umum (service) dapat menggunakan bahasa apa pun untuk menulis desain, yang ditulis menggunakan Python 3. Tentu saja pada FMZ juga mendukung untuk menjalankan program Python, atau Anda dapat menggunakan protokol ini sebagai hard drive untuk memberikan penemu platform perdagangan kuantitatif untuk akses API pertukaran yang tidak terbungkus. Setelah menjalankan protokol umum, mulailah mendengarkan:http://127.0.0.1:6666Dalam program protokol umum dapat digunakan untuk menentukan jalur tertentu, seperti/OKX"Saya tidak tahu apa yang terjadi.

3, contoh kebijakan meminta fungsi API FMZ

Ketika fungsi API dari platform (FMZ) dipanggil dalam kebijakan, program protokol umum akan menerima permintaan dari host. Alat debugging platform juga dapat diuji, misalnya:

Halaman alat debugging:

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

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

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

POST /OKX HTTP/1.1 
{
    "access_key":"xxx",
    "method":"ticker",
    "nonce":1730275031047002000,
    "params":{"symbol":"LTC_USDT"},
    "secret_key":"xxx"
}
  • access_key: Kunci rahasia pertukaran yang dikonfigurasi pada platform "konfigurasi bursa" di atas
  • secret_key: Kunci rahasia pertukaran yang dikonfigurasi pada platform "konfigurasi bursa" di atas
  • method: terkait dengan panggilan antarmuka dalam kebijakan, panggilanexchange.GetTicker()Saya tidak tahu apa yang terjadi.methodIni adalahticker
  • Nonce: Waktu saat permintaan terjadi.
  • Parameter: Parameter yang terkait dengan panggilan antarmuka yang terjadi dalam kebijakan, dalam hal iniexchange.GetTicker()Saat dihubungi, parameter yang terkait adalah:{"symbol":"LTC_USDT"}

4. Prosedur protokol umum untuk mengakses interface pertukaran

Ketika program protokol umum menerima permintaan dari administrator, informasi yang dibawa dalam permintaan dapat diketahui: fungsi API platform yang diminta oleh kebijakan (termasuk informasi parameter), kunci rahasia pertukaran, dll.

Berdasarkan informasi ini, program protokol umum dapat mengakses antarmuka bursa, mendapatkan data yang dibutuhkan, 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, diminta langsung dalam prosedur protokol umum.
  • Antarmuka pribadi: Antarmuka yang membutuhkan verifikasi tanda tangan, yang membutuhkan implementasi tanda tangan dalam proses protokol umum, dan kemudian meminta antarmuka API dari pertukaran ini.

Prosedur protokol umum menerima data respons antarmuka bursa untuk diproses lebih lanjut dan membentuk data yang diharapkan oleh penjaga (disebutkan di bawah ini). Referensi OKX Exchange, implementasi kelas CustomProtocolOKX dalam paradigma protokol umum PythonGetTickerGetAccountFungsi sama.

5. Prosesor protokol umum mengirim data ke administrator

Ketika sebuah program protokol umum mengakses antarmuka API dari sebuah bursa, melakukan beberapa operasi atau mendapatkan beberapa data, maka hasilnya harus dikembalikan kepada administrator.

Data umpan balik ke administrator berbeda-beda tergantung pada antarmuka yang dipanggil oleh kebijakan, dan pertama-tama dibagi menjadi dua kategori:

  • Perangkat lunak yang digunakan untuk mengaktifkan aplikasi ini adalah:

    {
        "data": null,  // "data" can be of any type 
        "raw": null    // "raw" can be of any type 
    }
    
    • data: struktur spesifik dari bidang ini dan permintaan yang diterima oleh protokol umummethodUntuk struktur data yang digunakan untuk membangun fungsi API platform FMZ, semua antarmuka akan terdaftar di bawah ini.
    • raw: Bidang ini dapat mengirimkan data mentah dari tanggapan API pertukaran, sepertiexchange.GetTicker()Fungsi yang dikembalikan oleh struktur ticker, yang tercatat dalam kolom Info dari struktur ticker adalahrawkolom dandataData dari bidang; fungsi API platform, beberapa tidak membutuhkan data ini.
  • Kesalahan panggilan protokol umum ke antarmuka pertukaran (kesalahan operasi, kesalahan jaringan, dll.)

    {
        "error": ""    // "error" contains an error message as a string
    }
    
    • error: error message, yang akan muncul di log error pada area log halaman seperti FMZ, debug tool, dll.

Data respons protokol umum yang diterima oleh program strategi demonstrasi:

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

img

6. Konvensi struktur data dalam protokol umum

Hal di atas adalah proses singkat yang dilakukan oleh program protokol umum yang terlibat dalam mengakses (FMZ unwrapped) API pertukaran, yang hanya menjelaskan tentang panggilan dalam alat debugging platform (FMZ).exchange.GetTicker()Proses saat fungsi. Berikut ini akan dijelaskan secara rinci tentang detail interaksi dari semua fungsi API platform.

Platform ini membungkus fungsi yang dibagikan oleh berbagai bursa, semuanya disatukan menjadi suatu fungsi, seperti fungsi GetTicker, yang meminta informasi pasar jenis tertentu saat ini, yang pada dasarnya adalah API yang tersedia untuk semua bursa. Jadi ketika dalam contoh kebijakan mengakses antarmuka API yang dibungkus oleh platform, administrator akan mengirim permintaan ke plugin "General Protocol" (yang telah disebutkan di atas):

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

Ketika fungsi API yang terbungkus oleh platform pencipta yang berbeda (misalnya GetTicker) dipanggil dalam kebijakan, format permintaan yang dikirimkan oleh host ke protokol umum juga akan berbeda. Data dalam JSON hanya berbeda dalam hal ini.methoddanparams❖ Ketika merancang protokol umum, Anda harus menggunakanmethodKonten untuk melakukan operasi tertentu. Berikut adalah skenario permintaan-respon dari semua antarmuka.

Bursa Saham

Misalnya, pasangan transaksi saat ini adalah:ETH_USDTData yang diharapkan oleh administrator untuk dijawab oleh protokol umum terutama ditulis di bidang data, atau dapat menambahkan bidang mentah untuk mencatat data asli antarmuka pertukaran.

  • GetTicker

    • Metode yang digunakan adalah:
    • Params field:
      {"symbol":"ETH_USDT"}
      
    • Data yang diharapkan oleh pengelola protokol umum adalah:
      {
          "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

    • metode bidang: depth
    • Params field:
      {"limit":"30","symbol":"ETH_USDT"}
      
    • Data yang diharapkan oleh pengelola protokol umum adalah:
      {
          "data" : {
              "time" : 1500793319499,
              "asks" : [
                  [1000, 0.5], [1001, 0.23], [1004, 2.1]
                  // ... 
              ],
              "bids" : [
                  [999, 0.25], [998, 0.8], [995, 1.4]
                  // ... 
              ]
          }
      }
      
  • GetTrades

    • metode bidang: trades
    • Params field:
      {"symbol":"eth_usdt"}
      
    • Data yang diharapkan oleh pengelola protokol umum adalah:
      { 
          "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

    • metode kolom: records
    • Params field:
      {
          "limit":"500",
          "period":"60",          // 60分钟
          "symbol":"ETH_USDT"
      }
      
    • Data yang diharapkan oleh pengelola protokol umum adalah:
      {
          "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],
                  // ...
          ]
      }
      
  • GetMarketsMenunggu

    • metode bidang:""
    • Params field:
      {}
      
    • Data yang diharapkan oleh pengelola protokol umum adalah:
      {}
      
  • GetTickersMenunggu

    • metode bidang:""
    • Params field:
      {}
      
    • Data yang diharapkan oleh pengelola protokol umum adalah:
      {}
      
  • DapatkanAkun

    • metode kolom: accounts
    • Params field:
      {}
      
    • Data yang diharapkan oleh pengelola protokol umum adalah:
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • GetAssets

    • Metode bidang: Aset-aset
    • Params field:
      {}
      
    • Data yang diharapkan oleh pengelola protokol umum adalah:
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"},
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • CreateOrder / Beli / Jual

    • metode bidang: trade
    • Params field:
      {"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
      
    • Data yang diharapkan oleh pengelola protokol umum adalah:
      {
          "data": {
              "id": "BTC-USDT,123456"
          }
      }
      
  • GetOrders

    • metode bidang: orders
    • Params field:
      {"symbol":"ETH_USDT"}
      
    • Data yang diharapkan oleh pengelola protokol umum adalah:
      {
          "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

    • Metode: Pengaturan
    • Params field:
      {
          "id":"ETH-USDT,123456",       // 策略中调用:exchange.GetOrder("ETH-USDT,123456")
          "symbol":"ETH_USDT"
      }
      
    • Data yang diharapkan oleh pengelola protokol umum adalah:
      { 
          "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

    • metode bidang: historyorders
    • Params field:
      {"limit":0,"since":0,"symbol":"ETH_USDT"}
      
    • Data yang diharapkan oleh pengelola protokol umum adalah:
      {
          "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

    • Metode: Menghapus metode
    • Params field:
      {"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
      
    • Data yang diharapkan oleh pengelola protokol umum adalah:
      {
          "data": true    // 只要该JSON中没有error字段,都默认为撤单成功
      }
      
  • IO

    exchange.IO函数用于直接访问交易所接口,例如我们以GET /api/v5/trade/orders-pending, 参数:instType=SPOT,instId=ETH-USDTContoh:

    // 策略实例中调用
    exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")
    
    • Metode adalah:"__api_/api/v5/trade/orders-pending"Konten bidang metode dimulai dengan _api_, yang berarti ini dipicu oleh panggilan fungsi exchange.IO dalam contoh kebijakan.
    • Params field:
      {"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
      
    • Data yang diharapkan oleh pengelola protokol umum adalah:
      {
          "data": {"code": "0", "data": [], "msg": ""}    // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
      }
      
  • lainnya Fungsi API platform penemu lain yang digunakan dalam contoh strategi, misalnya:exchange.Go()exchange.GetRawJSON()Fungsi seperti tidak perlu dibungkus, cara panggilan, fungsi tidak berubah.

Bursa futures

Selain mendukung fungsi dari semua bursa langsung, ada beberapa fungsi API yang spesifik untuk bursa berjangka.

Menunggu

  • DapatkanPosisi
  • SetMarginLevel
  • GetFundings

Contoh protokol umum versi Python

REST General Protocol plug-in mengakses OKX Exchange REST API interface, yang dibungkus sebagai obyek pertukaran langsung. Implementasi sebuah antarmuka publik untuk request and reply data wrapping. Dengan demikian, pengguna dapat menggunakan aplikasi ini untuk mengoptimalkan akses ke situs web. Contoh ini terutama untuk belajar pengujian, sementara sisanya menggunakan data simulasi yang dijawab langsung ke administrator untuk pengujian.

import http.server
import socketserver
import json
import urllib.request
import urllib.error
import argparse
import ssl
import hmac
import hashlib
import base64

from datetime import datetime

ssl._create_default_https_context = ssl._create_unverified_context

class BaseProtocol:
    ERR_NOT_SUPPORT = {"error": "not support"}

    def __init__(self, apiBase, accessKey, secretKey):
        self._apiBase = apiBase
        self._accessKey = accessKey
        self._secretKey = secretKey


    def _httpRequest(self, method, path, query="", params={}, addHeaders={}):
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6', 
            'Content-Type': 'application/json; charset=UTF-8'
        }

        # add headers
        for key in addHeaders:
            headers[key] = addHeaders[key]

        if method == "GET":
            url = f"{self._apiBase}{path}?{query}" if query != "" else f"{self._apiBase}{path}"
            req = urllib.request.Request(url, method=method, headers=headers)
        else:
            url = f"{self._apiBase}{path}"
            req = urllib.request.Request(url, json.dumps(params, separators=(',', ':')).encode('utf-8'), method=method, headers=headers)
        
        print(f'send request by protocol: {self.exName}, req:', req.method, req.full_url, req.headers, req.data, "\n")

        try:
            with urllib.request.urlopen(req) as resp:
                data = json.loads(resp.read())
        except json.JSONDecodeError:
            data = {"error": "Invalid JSON response"}
        except urllib.error.HTTPError as e:
            data = {"error": f"HTTP error: {e.code}"}
        except urllib.error.URLError as e:
            data = {"error": f"URL error: {e.reason}"}
        except Exception as e:
            data = {"error": f"Exception occurred: {str(e)}"}

        print(f'protocol response received: {self.exName}, resp:', data, "\n")

        return data
    

    def GetTickers(self):
        return self.ERR_NOT_SUPPORT


    def GetMarkets(self):
        return self.ERR_NOT_SUPPORT


    def GetTicker(self, symbol):
        return self.ERR_NOT_SUPPORT


    def GetDepth(self, symbol=""):
        return self.ERR_NOT_SUPPORT


    def GetTrades(self, symbol=""):
        return self.ERR_NOT_SUPPORT


    def GetRecords(self, symbol, period, limit):
        return self.ERR_NOT_SUPPORT


    def GetAssets(self):
        return self.ERR_NOT_SUPPORT


    def GetAccount(self):
        return self.ERR_NOT_SUPPORT


    def CreateOrder(self, symbol, side, price, amount):
        return self.ERR_NOT_SUPPORT


    def GetOrders(self, symbol=""):
        return self.ERR_NOT_SUPPORT


    def GetOrder(self, orderId):
        return self.ERR_NOT_SUPPORT


    def CancelOrder(self, orderId):
        return self.ERR_NOT_SUPPORT


    def GetHistoryOrders(self, symbol, since, limit):
        return self.ERR_NOT_SUPPORT


    def GetPostions(self, symbol=""):
        return self.ERR_NOT_SUPPORT


    def SetMarginLevel(self, symbol, marginLevel):
        return self.ERR_NOT_SUPPORT


    def GetFundings(self, symbol=""):
        return self.ERR_NOT_SUPPORT


    def IO(self, params):
        return self.ERR_NOT_SUPPORT


class ProtocolFactory:
    @staticmethod
    def createExWrapper(apiBase, accessKey, secretKey, exName) -> BaseProtocol:
        if exName == "OKX":
            return CustomProtocolOKX(apiBase, accessKey, secretKey, exName)
        else:
            raise ValueError(f'Unknown exName: {exName}')


class CustomProtocolOKX(BaseProtocol):
    """
    CustomProtocolOKX - OKX API Wrapper

    # TODO: add information.
    """

    def __init__(self, apiBase, accessKey, secretKey, exName):
        secretKeyList = secretKey.split(",")
        self.exName = exName
        self._x_simulated_trading = 0
        if len(secretKeyList) > 1:
            self._passphrase = secretKeyList[1]
            if len(secretKeyList) > 2:
                if secretKeyList[2] == "simulate":
                    self._x_simulated_trading = 1
        else:
            raise ValueError(f"{self.exName}: invalid secretKey format.")
        super().__init__(apiBase, accessKey, secretKeyList[0])


    def getCurrencys(self, symbol):
        baseCurrency, quoteCurrency = "", ""
        arrCurrency = symbol.split("_")
        if len(arrCurrency) == 2:
            baseCurrency = arrCurrency[0]
            quoteCurrency = arrCurrency[1]
        return baseCurrency, quoteCurrency


    def getSymbol(self, instrument):
        arrCurrency = instrument.split("-")
        if len(arrCurrency) == 2:
            baseCurrency = arrCurrency[0]
            quoteCurrency = arrCurrency[1]
        else:
            raise ValueError(f"{self.exName}: invalid instrument: {instrument}")
        return f'{baseCurrency}_{quoteCurrency}'


    def callUnsignedAPI(self, httpMethod, path, query="", params={}):
        return self._httpRequest(httpMethod, path, query, params)


    def callSignedAPI(self, httpMethod, path, query="", params={}):
        strTime = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
        if httpMethod == "GET":
            jsonStr = json.dumps(params, separators=(',', ':')) if len(params) > 0 else ""
        else:
            jsonStr = json.dumps(params, separators=(',', ':')) if len(params) > 0 else "{}"
        message = f'{strTime}{httpMethod}{path}{jsonStr}'
        if httpMethod == "GET" and query != "":
            message = f'{strTime}{httpMethod}{path}?{query}{jsonStr}'
        mac = hmac.new(bytes(self._secretKey, encoding='utf8'), bytes(message, encoding='utf-8'), digestmod='sha256')
        signature = base64.b64encode(mac.digest())
        
        headers = {}
        if self._x_simulated_trading == 1:
            headers["x-simulated-trading"] = str(self._x_simulated_trading)
        headers["OK-ACCESS-KEY"] = self._accessKey
        headers["OK-ACCESS-PASSPHRASE"] = self._passphrase
        headers["OK-ACCESS-TIMESTAMP"] = strTime        
        headers["OK-ACCESS-SIGN"] = signature
        return self._httpRequest(httpMethod, path, query, params, headers)

    
    # Encapsulates requests to the exchange API.
    def GetTicker(self, symbol):
        """
        GET /api/v5/market/ticker , param: instId 
        """

        baseCurrency, quoteCurrency = self.getCurrencys(symbol)
        if baseCurrency == "" or quoteCurrency == "":
            return {"error": "invalid symbol"}

        path = "/api/v5/market/ticker"
        query = f'instId={baseCurrency}-{quoteCurrency}'
        data = self.callUnsignedAPI("GET", path, query=query)
        if "error" in data.keys() and "data" not in data.keys():
            return data

        ret_data = {}
        if data["code"] != "0" or not isinstance(data["data"], list):
            return {"error": json.dumps(data, ensure_ascii=False)}
        for tick in data["data"]:
            if not all(k in tick for k in ("instId", "bidPx", "askPx", "high24h", "low24h", "vol24h", "ts")):
                return {"error": json.dumps(data, ensure_ascii=False)}

            ret_data["symbol"] = self.getSymbol(tick["instId"])
            ret_data["buy"] = tick["bidPx"]
            ret_data["sell"] = tick["askPx"]
            ret_data["high"] = tick["high24h"]
            ret_data["low"] = tick["low24h"]
            ret_data["open"] = tick["open24h"]
            ret_data["last"] = tick["last"]
            ret_data["vol"] = tick["vol24h"]
            ret_data["time"] = tick["ts"]

        return {"data": ret_data, "raw": data}


    def GetDepth(self, symbol):
        """
        TODO: Implementation code
        """
        
        # Mock data for testing.
        ret_data = {            
            "time" : 1500793319499,
            "asks" : [
                [1000, 0.5], [1001, 0.23], [1004, 2.1]
            ],
            "bids" : [
                [999, 0.25], [998, 0.8], [995, 1.4]
            ]            
        }
        
        return {"data": ret_data}


    def GetTrades(self, symbol):
        """
        TODO: Implementation code
        """

        # Mock data for testing.
        ret_data = [
            {
                "id": 12232153,
                "time" : 1529919412968,
                "price": 1000,
                "amount": 0.5,
                "type": "buy",
            }, {
                "id": 12545664,
                "time" : 1529919412900,
                "price": 1001,
                "amount": 1,
                "type": "sell",
            }
        ]

        return {"data": ret_data}


    def GetRecords(self, symbol, period, limit):
        """
        TODO: Implementation code
        """

        # Mock data for testing.
        ret_data = [
            [1500793319, 1.1, 2.2, 3.3, 4.4, 5.5],
            [1500793259, 1.01, 2.02, 3.03, 4.04, 5.05],
        ]

        return {"data": ret_data}


    def GetMarkets(self):
        """
        TODO: Implementation code
        """

        ret_data = {}

        return {"data": ret_data}


    def GetTickers(self):
        """
        TODO: Implementation code
        """

        ret_data = {}

        return {"data": ret_data}


    def GetAccount(self):
        """
        GET /api/v5/account/balance
        """

        path = "/api/v5/account/balance"
        data = self.callSignedAPI("GET", path)

        ret_data = []
        if data["code"] != "0" or "data" not in data or not isinstance(data["data"], list):
            return {"error": json.dumps(data, ensure_ascii=False)}
        for ele in data["data"]:
            if "details" not in ele or not isinstance(ele["details"], list):
                return {"error": json.dumps(data, ensure_ascii=False)}
            for detail in ele["details"]:
                asset = {"currency": detail["ccy"], "free": detail["availEq"], "frozen": detail["ordFrozen"]}
                if detail["availEq"] == "":
                    asset["free"] = detail["availBal"]
                ret_data.append(asset)
        return {"data": ret_data, "raw": data}


    def GetAssets(self):
        """
        TODO: Implementation code
        """
        
        # Mock data for testing.
        ret_data = [
            {"currency": "TUSD", "free": "3000", "frozen": "0"},
            {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}
        ]

        return {"data": ret_data}


    def CreateOrder(self, symbol, side, price, amount):
        """
        TODO: Implementation code
        """
        
        # Mock data for testing.
        ret_data = {
            "id": "BTC-USDT,123456"
        }

        return {"data": ret_data}

    
    def GetOrders(self, symbol):
        """
        GET /api/v5/trade/orders-pending  instType SPOT instId  after limit
        """
        
        baseCurrency, quoteCurrency = self.getCurrencys(symbol)
        if baseCurrency == "" or quoteCurrency == "":
            return {"error": "invalid symbol"}

        path = "/api/v5/trade/orders-pending"
        after = ""
        limit = 100

        ret_data = []
        while True:
            query = f"instType=SPOT&instId={baseCurrency}-{quoteCurrency}&limit={limit}"
            if after != "":
                query = f"instType=SPOT&instId={baseCurrency}-{quoteCurrency}&limit={limit}&after={after}"
        
            data = self.callSignedAPI("GET", path, query=query)
            
            if data["code"] != "0" or not isinstance(data["data"], list):
                return {"error": json.dumps(data, ensure_ascii=False)}
            for ele in data["data"]:
                order = {}

                order["id"] = f'{ele["instId"]},{ele["ordId"]}'
                order["symbol"] = f'{baseCurrency}-{quoteCurrency}'
                order["amount"] = ele["sz"]
                order["price"] = ele["px"]
                order["deal_amount"] = ele["accFillSz"]
                order["avg_price"] = 0 if ele["avgPx"] == "" else ele["avgPx"]
                order["type"] = "buy" if ele["side"] == "buy" else "sell"
                order["state"] = "pending"

                ret_data.append(order)
                after = ele["ordId"]

            if len(data["data"]) < limit:
                break

        return {"data": ret_data}


    def GetOrder(self, orderId):
        """
        TODO: Implementation code
        """
        
        # Mock data for testing.
        ret_data = {
            "id": "ETH-USDT,123456",
            "symbol": "ETH_USDT",
            "amount": 0.15,
            "price": 1002,
            "status": "pending",
            "deal_amount": 0,
            "type": "buy",
            "avg_price": 0,
        }

        return {"data": ret_data}


    def GetHistoryOrders(self, symbol, since, limit):
        """
        TODO: Implementation code
        """

        # Mock data for testing.
        ret_data = [
            {
                "id": "ETH-USDT,123456",
                "symbol": "ETH_USDT",
                "amount": 0.25,
                "price": 1005,
                "deal_amount": 0,
                "avg_price": 1000,
                "type": "buy",
                "status": "filled"
            }
        ]

        return {"data": ret_data}


    def CancelOrder(self, orderId):
        """
        TODO: Implementation code
        """

        # Mock data for testing.
        ret_data = True

        return {"data": ret_data}


    def IO(self, httpMethod, path, params={}):
        if httpMethod == "GET":
            query = urllib.parse.urlencode(params)
            data = self.callSignedAPI(httpMethod, path, query=query)
        else:
            data = self.callSignedAPI(httpMethod, path, params=params)
        
        if data["code"] != "0":
            return {"error": json.dumps(data, ensure_ascii=False)}

        return {"data": data}


class HttpServer(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        self.request_body = None
        self.request_path = None
        super().__init__(*args, **kwargs)


    def log_message(self, format, *args):
        return 


    def _sendResponse(self, body):
        self.send_response(200)
        self.send_header('Content-type', 'application/json; charset=utf-8')
        self.end_headers()
        self.wfile.write(json.dumps(body).encode('utf-8'))


    def do_GET(self):
        # The FMZ.COM custom protocol only send GET method request
        self._sendResponse({"error": "not support GET method."})


    def do_POST(self):
        """
        Returns:
            json: success, {"data": ...}
            json: error,   {"error": ...}
        """

        contentLen = int(self.headers['Content-Length'])
        self.request_body = self.rfile.read(contentLen)
        self.request_path = self.path
        exName = self.request_path.lstrip("/")

        # Print the request received from the FMZ.COM robot
        print(f"--------- request received from the FMZ.COM robot: --------- \n {self.requestline} | Body: {self.request_body} | Headers: {self.headers} \n")

        try:
            data = json.loads(self.request_body)
        except json.JSONDecodeError:
            data = {"error": self.request_body.decode('utf-8')}
            self._sendResponse(data)
            return 

        # fault tolerant
        if not all(k in data for k in ("access_key", "secret_key", "method", "params")):
            data = {"error": "missing required parameters"}
            self._sendResponse(data)
            return

        respData = {}
        accessKey = data["access_key"]
        secretKey = data["secret_key"]
        method = data["method"]
        params = data["params"]
        exchange = ProtocolFactory.createExWrapper("https://www.okx.com", accessKey, secretKey, exName)

        if method == "ticker":
            symbol = str(params["symbol"]).upper()
            respData = exchange.GetTicker(symbol)
        elif method == "depth":
            symbol = str(params["symbol"]).upper()
            respData = exchange.GetDepth(symbol)
        elif method == "trades":
            symbol = str(params["symbol"]).upper()
            respData = exchange.GetTrades(symbol)
        elif method == "records":
            symbol = str(params["symbol"]).upper()
            period = int(params["period"])
            limit = int(params["limit"])
            respData = exchange.GetRecords(symbol, period, limit)
        elif method == "accounts":
            respData = exchange.GetAccount()
        elif method == "assets":
            respData = exchange.GetAssets()
        elif method == "trade":
            amount = float(params["amount"])
            price = float(params["price"])
            symbol = str(params["symbol"])
            tradeType = str(params["type"])
            respData = exchange.CreateOrder(symbol, tradeType, price, amount)
        elif method == "orders":
            symbol = str(params["symbol"]).upper()
            respData = exchange.GetOrders(symbol)
        elif method == "order":
            orderId = str(params["id"])
            respData = exchange.GetOrder(orderId)
        elif method == "historyorders":
            symbol = str(params["symbol"])
            since = int(params["since"])
            limit = int(params["limit"])
            respData = exchange.GetHistoryOrders(symbol, since, limit)
        elif method == "cancel":
            orderId = str(params["id"])
            respData = exchange.CancelOrder(orderId)
        elif method[:6] == "__api_":
            respData = exchange.IO(self.headers["Http-Method"], method[6:], params)
        else:
            respData = {"error": f'invalid method: {method}'}

        # Print the response to send to FMZ.COM robot
        print(f"response to send to FMZ.COM robot: {respData} \n")

        self._sendResponse(respData)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Run a FMZ.COM custom protocol plugin.")
    parser.add_argument("--port", type=int, default=6666, help="Port to run the server on.")
    parser.add_argument("--address", type=str, default="localhost", help="Address to bind the server to.")
    args = parser.parse_args() 

    with socketserver.TCPServer((args.address, args.port), HttpServer) as httpd:
        print(f"running... {args.address}:{args.port}", "\n")
        httpd.serve_forever()

Lebih banyak