Sumber dimuat naik... memuat...

Pencipta Panduan Akses Platform Perdagangan Kuantitatif

Penulis:Pencipta Kuantiti - Impian Kecil, Dicipta: 2024-10-29 14:37:56, Dikemas kini: 2024-11-12 21:58:55

[TOC]

img

Platform dagangan Kuantitatif Pencipta menyokong banyak pertukaran mata wang kripto, dan merangkumi pertukaran utama di pasaran. Walau bagaimanapun, masih ada banyak pertukaran yang belum terbungkus, pengguna yang memerlukan penggunaan pertukaran ini boleh mengaksesnya melalui protokol umum Kuantitatif Pencipta.RESTperjanjian atauFIXPlatform perjanjian juga boleh diakses.

Artikel ini akan membincangkanRESTSebagai contoh, akses protokol, menerangkan bagaimana menggunakan protokol umum platform dagangan kuantitatif pencipta untuk membungkus dan mengakses API bursa OKX.

  • Proses kerja Perjanjian Umum ialah: Proses permintaan: Contoh dasar yang dijalankan pada pengurus -> Prosedur Protokol Umum -> API Bursa Proses jawapan: API Bursa -> Prosedur Protokol Umum -> Contoh Dasar yang dijalankan pada Pengurus

1. Mengatur Bursa

Laman di mana pencipta menyusun platform pertukaran kuantitatif:

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

img

  • Pilih protokol: Pilih "Prosedur Umum".
  • Alamat Perkhidmatan: Program Protokol Umum pada dasarnya adalah perkhidmatan RPC Oleh itu, anda perlu menetapkan alamat perkhidmatan, port, dan sebagainya dengan jelas semasa mengkonfigurasi bursa.托管者上运行的策略实例 -> 通用协议程序Dalam proses ini, pentadbir tahu di mana untuk mengakses prosedur protokol umum. Contohnya:http://127.0.0.1:6666/OKXBiasanya, program protokol umum dijalankan pada peranti yang sama dengan hoster (server), jadi alamat perkhidmatan ditulis pada komputer tempatan (localhost), port digunakan untuk sistem yang tidak ditempati.
  • Kunci Akses:托管者上运行的策略实例 -> 通用协议程序Dalam proses ini, maklumat konfigurasi bursa yang dihantar.
  • Kunci Rahsia:托管者上运行的策略实例 -> 通用协议程序Dalam proses ini, maklumat konfigurasi bursa yang dihantar.
  • Tag: kumpulan Tag objek bursa pada platform dagangan kuantiti pencipta, digunakan untuk mengenal pasti objek bursa tertentu.

Di bawah ini adalah skrin konfigurasi OKX plugin yang didedahkan dalam artikel:

img

OKX Exchange: Maklumat mengenai perisian kunci rahsia

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

2. Menghantar pentadbir dan protokol umum (plugin)

  • 1. Pengurus Mana-mana strategi yang dijalankan pada platform dagangan kuantitatif pencipta mesti digunakan oleh pengurus, yang boleh dilihat dalam tutorial platform, yang tidak akan dibincangkan di sini.

  • 2. Program protokol umum (plugin) Pengurus dan protokol umum biasanya digunakan pada peranti yang sama, program protokol umum (services) boleh menggunakan mana-mana bahasa untuk menulis reka bentuk, yang ditulis menggunakan Python 3. Sudah tentu, program Python juga disokong di FMZ, dan protokol ini boleh dijalankan sebagai cakera sebenar untuk memberi pencipta platform perdagangan kuantitatif sokongan untuk akses API pertukaran yang tidak terbungkus. Selepas menjalankan prosedur protokol umum, mula dengar:http://127.0.0.1:6666Dalam program protokol umum, anda boleh menggunakan laluan tertentu, seperti/OKXMenguruskan.

3, contoh dasar meminta fungsi API FMZ

Apabila fungsi API platform (FMZ) dipanggil dalam dasar, program protokol umum akan menerima permintaan dari hosts. Alat debugging platform juga boleh diuji, seperti:

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 rahsia pertukaran yang dikonfigurasikan semasa platform "mengkonfigurasi bursa" di atas
  • secret_key: Kunci rahsia pertukaran yang dikonfigurasikan semasa platform "mengkonfigurasi bursa" di atas
  • method: berkaitan dengan panggilan antara muka dalam dasar, panggilanexchange.GetTicker()Saya tidak tahu apa yang berlaku.methodMaksudnya,ticker
  • Nonce: Tempoh permintaan berlaku.
  • Parameter: Parameter yang berkaitan dengan panggilan antarmuka yang berlaku dalam dasar, contoh iniexchange.GetTicker()Apabila dipanggil, parameter yang berkaitan adalah:{"symbol":"LTC_USDT"}

4. Prosedur protokol umum untuk mengakses antara muka bursa

Apabila program protokol umum menerima permintaan dari pemegang simpanan, ia dapat mengetahui maklumat yang dibawa dalam permintaan: fungsi API platform yang diminta oleh dasar (termasuk maklumat parameter), kunci rahsia pertukaran, dan lain-lain.

Mengikut maklumat ini, program protokol umum boleh mengakses antara muka bursa, mendapatkan data yang diperlukan atau melakukan beberapa operasi.

Selalunya antara muka bursa 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, yang diminta secara langsung dalam prosedur protokol umum.
  • Antara muka persendirian: antara muka yang memerlukan pengesahan tandatangan, yang memerlukan pengesahan dalam prosedur protokol umum, dan kemudian meminta antara muka API pertukaran ini.

Prosedur protokol umum menerima data tindak balas antara muka bursa untuk pemprosesan lanjut, yang membentuk data yang diharapkan oleh pemegang (dijelaskan di bawah). Rujukan kepada OKX Bursa Tukar, dalam penerapan kelas CustomProtocolOKX dalam paradigma protokol umum PythonGetTickerGetAccountFungsi sama.

5. Prosedur protokol umum membalas data kepada pengurus

Apabila program protokol umum mengakses antara muka API bursa, menjalankan beberapa operasi atau mendapatkan data tertentu, ia perlu memberi maklum balas kepada pengurus.

Maklumat maklum balas kepada pentadbir berbeza-beza mengikut antara muka yang dipanggil oleh dasar, dan dibahagikan kepada dua kategori pertama:

  • Perkhidmatan protokol umum berjaya memanggil antara muka bursa:

    {
        "data": null,  // "data" can be of any type 
        "raw": null    // "raw" can be of any type 
    }
    
    • data: struktur tertentu bagi medan ini dan permintaan yang diterima oleh prosedur protokol umummethodMengenai struktur data yang digunakan untuk membina fungsi API platform FMZ, semua antara muka disenaraikan di bawah.
    • RAW: medan ini boleh menghantar data mentah daripada jawapan API pertukaran, sepertiexchange.GetTicker()Struktur ticker yang dikembalikan oleh fungsi, yang dicatatkan dalam medan Info struktur ticker ialahrawBilik dandataData bidang; fungsi API platform, ada yang tidak memerlukan data ini.
  • Kegagalan panggilan antara muka bursa oleh program protokol umum (kesalahan operasi, kesalahan rangkaian, dan lain-lain)

    {
        "error": ""    // "error" contains an error message as a string
    }
    
    • error: Mesej ralat yang akan dipaparkan dalam log ralat di bahagian log halaman seperti cakera sebenar, alat debugging, dan lain-lain.

Data tindak balas protokol umum yang diterima oleh program strategi demonstrasi:

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

img

6. Konvensyen struktur data dalam protokol umum

Perkara di atas adalah proses ringkas yang melibatkan program protokol umum untuk mengakses (FMZ tidak dibungkus) API bursa, yang hanya menerangkan panggilan dalam alat debugging platform (FMZ).exchange.GetTicker()Proses semasa fungsi. Perincian interaksi semua fungsi API platform akan dijelaskan di bawah.

Platform ini merangkumi ciri-ciri yang dikongsi oleh pelbagai bursa dan semuanya disatukan ke dalam satu fungsi, seperti fungsi GetTicker, yang meminta maklumat pasaran semasa dalam pelbagai jenis, yang pada dasarnya adalah API yang terdapat di semua bursa. Oleh itu, ketika mengakses antara muka API yang dikemas dalam platform dalam contoh dasar, pengurus akan menghantar permintaan kepada pemalam "protokol umum" (seperti yang telah disebutkan di atas):

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

Apabila fungsi API yang dibungkus oleh platform pencipta yang berbeza (seperti GetTicker) dipanggil dalam dasar, format permintaan yang dihantar oleh hos kepada protokol umum juga akan berbeza. Data dalam badan (JSON) hanya berbeza keranamethoddanparams◎ Apabila merancang protokol umum, anda perlu menggunakanmethodBerikut adalah kesemua antara muka yang mempunyai situasi permintaan-jawapan.

Bursa Saham

Sebagai contoh, pasangan dagangan semasa adalah:ETH_USDTData yang dijangkakan oleh pemegang protokol umum untuk dijawab terutamanya ditulis dalam medan data, atau boleh menambah medan mentah untuk merakam data asal antara muka pertukaran.

  • GetTicker

    • medan method: penanda
    • Params bidang:
      {"symbol":"ETH_USDT"}
      
    • Data yang diharapkan oleh pengurus untuk dijawab oleh protokol umum ialah:
      {
          "data": {
              "symbol": "ETH_USDT",      // 对应GetTicker函数返回的Ticker结构中的Symbol字段
              "buy": "2922.18",          // ...对应Buy字段
              "sell": "2922.19", 
              "high": "2955", 
              "low": "2775.15", 
              "open": "2787.72", 
              "last": "2922.18", 
              "vol": "249400.888156", 
              "time": "1731028903911"
          },
          "raw": {}                      // 可以增加一个raw字段记录交易所API接口应答的原始数据
      }
      
  • GetDepth

    • medan method: depth
    • Params bidang:
      {"limit":"30","symbol":"ETH_USDT"}
      
    • Data yang diharapkan oleh pengurus untuk dijawab oleh protokol umum ialah:
      {
          "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 method: trades
    • Params bidang:
      {"symbol":"eth_usdt"}
      
    • Data yang diharapkan oleh pengurus untuk dijawab oleh protokol umum ialah:
      { 
          "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 method: records
    • Params bidang:
      {
          "limit":"500",
          "period":"60",          // 60分钟
          "symbol":"ETH_USDT"
      }
      
    • Data yang diharapkan oleh pengurus untuk dijawab oleh protokol umum ialah:
      {
          "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],
                  // ...
          ]
      }
      
  • GetMarketsTelah dilaksanakan

    • medan method:""
    • Params bidang:
      {}
      
    • Data yang diharapkan oleh pengurus untuk dijawab oleh protokol umum ialah:
      {}
      
  • GetTickersTelah dilaksanakan

    • medan method:""
    • Params bidang:
      {}
      
    • Data yang diharapkan oleh pengurus untuk dijawab oleh protokol umum ialah:
      {}
      
  • DapatkanAkaun

    • medan method: accounts
    • Params bidang:
      {}
      
    • Data yang diharapkan oleh pengurus untuk dijawab oleh protokol umum ialah:
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • GetAssets

    • medan method: ketukan aset ketukan
    • Params bidang:
      {}
      
    • Data yang diharapkan oleh pengurus untuk dijawab oleh protokol umum ialah:
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"},
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • BuatOrder / Beli / Jual

    • medan method: trade
    • Params bidang:
      {"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
      
    • Data yang diharapkan oleh pengurus untuk dijawab oleh protokol umum ialah:
      {
          "data": {
              "id": "BTC-USDT,123456"
          }
      }
      
  • GetOrders

    • medan method: orders
    • Params bidang:
      {"symbol":"ETH_USDT"}
      
    • Data yang diharapkan oleh pengurus untuk dijawab oleh protokol umum ialah:
      {
          "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 method: order order
    • Params bidang:
      {
          "id":"ETH-USDT,123456",       // 策略中调用:exchange.GetOrder("ETH-USDT,123456")
          "symbol":"ETH_USDT"
      }
      
    • Data yang diharapkan oleh pengurus untuk dijawab oleh protokol umum ialah:
      { 
          "data": {
              "id": "ETH-USDT,123456",
              "symbol": "ETH_USDT"
              "amount": 0.15,
              "price": 1002,
              "status": "pending",    // "pending", "pre-submitted", "submitting", "submitted", "partial-filled", "filled", "closed", "finished", "partial-canceled", "canceled"
              "deal_amount": 0,
              "type": "buy",          // "buy"、"sell"
              "avg_price": 0,         // 如果交易所没有提供,在处理时可以赋值为0
          }
      }
      
  • GetHistoryOrders

    • medan method: historyorders
    • Params bidang:
      {"limit":0,"since":0,"symbol":"ETH_USDT"}
      
    • Data yang diharapkan oleh pengurus untuk dijawab oleh protokol umum ialah:
      {
          "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 method: →cancel →
    • Params bidang:
      {"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
      
    • Data yang diharapkan oleh pengurus untuk dijawab oleh protokol umum ialah:
      {
          "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")
    
    • medan method:"__api_/api/v5/trade/orders-pending"Kandungan bidang kaedah bermula dengan _api_, yang menunjukkan bahawa ini dipicu oleh panggilan fungsi exchange.IO dalam contoh dasar.
    • Params bidang:
      {"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
      
    • Data yang diharapkan oleh pengurus untuk dijawab oleh protokol umum ialah:
      {
          "data": {"code": "0", "data": [], "msg": ""}    // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
      }
      
  • Lain-lain Fungsi API platform pencipta lain yang digunakan dalam contoh strategi, seperti:exchange.Go()exchange.GetRawJSON()Fungsi seperti tidak perlu dibungkus, cara dipanggil, fungsi tidak berubah.

Bursa niaga hadapan

Di samping menyokong semua fungsi bursa masa hadapan, terdapat beberapa fungsi API yang khusus untuk bursa masa hadapan.

Telah dilaksanakan

  • DapatkanPosisi
  • SetMarginLevel
  • GetFundings

Contoh protokol umum versi Python

REST protokol umum yang digunakan untuk mengakses OKX pertukaran REST API antara muka, yang dibungkus sebagai objek pertukaran langsung. Memenuhi antara muka awam dengan pembungkusan data permintaan dan tindak balas. Mencapai penandatanganan antara muka peribadi, pembungkusan data permintaan dan tindak balas. Perumpamaan ini adalah untuk pembelajaran ujian, sementara yang lain menggunakan data simulasi yang dijawab secara langsung kepada hos untuk ujian.

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

from datetime import datetime

ssl._create_default_https_context = ssl._create_unverified_context

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

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


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

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

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

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

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

        return data
    

    def GetTickers(self):
        return self.ERR_NOT_SUPPORT


    def GetMarkets(self):
        return self.ERR_NOT_SUPPORT


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


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


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


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


    def GetAssets(self):
        return self.ERR_NOT_SUPPORT


    def GetAccount(self):
        return self.ERR_NOT_SUPPORT


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


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


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


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


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


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


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


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


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


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


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

    # TODO: add information.
    """

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


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


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


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


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

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

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

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

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

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

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


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


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

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

        return {"data": ret_data}


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

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

        return {"data": ret_data}


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

        ret_data = {}

        return {"data": ret_data}


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

        ret_data = {}

        return {"data": ret_data}


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

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

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


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

        return {"data": ret_data}


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

        return {"data": ret_data}

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

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

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

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

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

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

        return {"data": ret_data}


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

        return {"data": ret_data}


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

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

        return {"data": ret_data}


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

        # Mock data for testing.
        ret_data = True

        return {"data": ret_data}


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

        return {"data": data}


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


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


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


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


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

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

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

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

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

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

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

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

        self._sendResponse(respData)


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

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

Lebih lanjut