В процессе загрузки ресурсов... загрузка...

Инвесторы по количественной торговле

Автор:Изобретатели количественного измерения - мечты, Создано: 2024-10-29 14:37:56, Обновлено: 2024-11-12 21:58:55

[TOC]

img

Изобретательская квантовая торговая платформа поддерживает множество криптовалютных бирж и охватывает основные биржи на рынке. Тем не менее, есть еще много обменных площадок, которые не были охвачены, и для пользователей, которые хотят использовать эти биржи, доступ к ним доступен через общий протокол квантования Изобретателя.Отдыхсоглашение илиФИКСТакже доступны платформы соглашения.

В статьеОтдыхПротокольный доступ, например, объясняет, как использовать общий протокол квантовой торговой платформы изобретателя для упаковки и доступа к API биржи OKX.

  • Рабочий процесс Генерального соглашения: Процесс запроса: Примеры стратегий, работающих на хранителе -> Процедуры общих протоколов -> API биржи Процесс ответа: API биржи -> Процедура общих протоколов -> Примеры стратегий, работающих на хранителе

1, Конфигурация биржи

На странице, на которой изобретатель настраивает обменную площадку для количественной торговли:

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

img

  • Выбор протокола: выберите "Общий протокол".
  • Адрес сервиса: Процедура общих протоколов по своей сути является службой RPC Поэтому при настройке биржи необходимо четко указать адрес сервиса, порт.托管者上运行的策略实例 -> 通用协议程序В процессе администраторы знают, где получить доступ к протоколу. Например:http://127.0.0.1:6666/OKXОбычно протокольные программы и хост работают на одном устройстве (сервер), поэтому адрес сервиса записывается на локальный хост, а порт используется на незанятом устройстве.
  • Ключ доступа:托管者上运行的策略实例 -> 通用协议程序В процессе передачи информация о конфигурации биржи.
  • Секретный ключ:托管者上运行的策略实例 -> 通用协议程序В процессе передачи информация о конфигурации биржи.
  • Название: Объекты бирж на платформах количественной торговли изобретателей, используемые для обозначения объектов бирж.

Вот скриншот конфигурации плагина OKX, опубликованный в статье:

img

Конфигурация секретного ключа на бирже OKX:

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

2. Разверните хостеры и плагины.

  • 1 - хранитель Любая стратегия, выполняемая на платформе количественного обмена изобретателей, требует развертывания администратора, который может использоваться в руководстве к платформе.

  • 2, Протоколы (плюгины) Хост и общий протокол обычно развертываются на одном устройстве, и программы общих протоколов (сервисы) могут использовать любой язык для написания дизайна. Конечно, на FMZ также поддерживается работа с программами Python, но также можно использовать этот протокол как реальный диск, чтобы предоставить изобретателям поддержку количественной платформы для доступа к некомплектованным API бирж. После того, как протокол запустили, начали прослушивать:http://127.0.0.1:6666Протоколы могут использоваться для определения конкретных путей, например:/OKXПроцесс обработки.

3, Пример стратегии запроса API-функции FMZ

При вызове API-функции платформы (FMZ) в политике, протоколы общих протоколов получают запросы от хостеров. Использование инструментов для дешифровки платформы также можно проверить, например:

На странице с инструментами для отладки:

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

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

Звонокexchange.GetTicker()Функция, провайдер протокола получил запрос:

POST /OKX HTTP/1.1 
{
    "access_key":"xxx",
    "method":"ticker",
    "nonce":1730275031047002000,
    "params":{"symbol":"LTC_USDT"},
    "secret_key":"xxx"
}
  • access_key: секретный ключ биржи, установленный при "конфигурации биржи" платформы в приведенном выше тексте
  • secret_key: секретный ключ биржи, установленный при "конфигурации биржи" платформы в приведенном выше тексте
  • method: связано с вызовом интерфейса в политике, вызовexchange.GetTicker()В этом случае,methodТо естьticker
  • Nonce: время, когда произошел запрос.
  • params: параметры, связанные с вызовом интерфейса в политике, в данном случаеexchange.GetTicker()При вызове соответствующие параметры:{"symbol":"LTC_USDT"}

4, Процедура общих протоколов для доступа к интерфейсу биржи

При получении запроса от хранителя протокольная программа может узнать информацию, содержащуюся в запросе: платформы API (включая информацию о параметрах), секретные ключи биржи и т. д.

На основе этой информации протоколы могут получить доступ к интерфейсу биржи, получить нужную информацию или выполнить определенные действия.

Обычно интерфейсы бирж имеют такие методы, как GET / POST / PUT / DELETE, которые подразделяются на публичные и частные интерфейсы.

  • Общественный интерфейс: интерфейс, не требующий подтверждения подписи, запрошенный непосредственно в процедуре общих протоколов.
  • Частные интерфейсы: интерфейсы, которые требуют проверки подписи, которые требуют подписи в процессе общих протоколов, а затем запрашивают API-интерфейсы этих бирж.

Процедура общих протоколов получает данные от интерфейса биржи для дальнейшей обработки, составляя данные, ожидаемые хранителем (см. ниже). Ссылка на ОКХ-обмен, реализация класса CustomProtocolOKX в Python general protocol paradigmGetTickerGetAccountРавнозначная функция.

5. Протокол передает данные администратору.

После того, как протокольный провайдер заходит в API-интерфейс биржи, выполняет определенные действия или получает определенные данные, он должен дать отзывы хранителю.

Отзывы к администраторам различаются в зависимости от интерфейса, который используется для вызова политики.

  • Процедура общих протоколов вызвала интерфейс биржи успешно:

    {
        "data": null,  // "data" can be of any type 
        "raw": null    // "raw" can be of any type 
    }
    
    • data: конкретная структура этого поля и запросы, полученные процессоромmethodСюда относятся все интерфейсы, используемые для конструирования данных, которые в конечном итоге возвращаются функциями API платформы FMZ.
    • raw: поле, которое может передавать исходные данные от ответа на API биржи, напримерexchange.GetTicker()Функция возвращает структуру тикера, которая записывается в поле InforawПоле иdataДанные по поле; API-функции платформы, некоторые из которых не требуют этих данных.
  • Неудача вызова обменного интерфейса протоколом общих протоколов (процессная ошибка, сетевая ошибка и т.д.)

    {
        "error": ""    // "error" contains an error message as a string
    }
    
    • error: сообщение об ошибке, которое будет отображаться в журнале ошибок в разделе журналов страниц, таких как диск платформы FMZ, инструменты для дешифровки и т. Д.;

Показать данные об ответах на протоколы, полученные программой:

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

img

6. Соглашения о структуре данных в общих протоколах

Выше приведен краткий процесс участия протокола в доступе к API биржи (FMZ unwrapped), который объясняет только вызовы в инструментах дешифровки платформы FMZ.exchange.GetTicker()Процесс при функции. Ниже приведены подробности взаимодействия всех платформенных API-функций.

Платформа объединяет функции, общие для всех бирж, и объединяет их в одну функцию, например, функцию GetTicker, которая запрашивает текущую информацию о рынке определенного сорта, что является API, доступной практически для всех бирж. Поэтому, когда в случае стратегии пользователь получает доступ к API-интерфейсу, вложенному в платформу, администратор отправляет запрос плагину "Общий протокол" (как упоминалось выше):

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

При вызове API-функции, упакованной в различные платформы изобретателей (например, GetTicker), в политике также будет различаться формат запроса, который хост отправляет в общий протокол.methodиparams◊ При разработке протокола, используйтеmethodСодержимое интерфейса может быть использовано для выполнения конкретных операций.

Настольная биржа

Например, текущая торговая пара:ETH_USDTВ дальнейшем больше не упоминается. Данные, на которые хозяин ожидает ответить в соответствии с общим протоколом, записываются в основном в поле data, а также может быть добавлено поле raw для записи исходных данных интерфейса биржи.

  • GetTicker

    • Поле method: ticker
    • Params поле:
      {"symbol":"ETH_USDT"}
      
    • Услуги, предоставляемые в рамках ГПД, включают:
      {
          "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接口应答的原始数据
      }
      
  • ПолучитьГлубину

    • Поле method: depth
    • Params поле:
      {"limit":"30","symbol":"ETH_USDT"}
      
    • Услуги, предоставляемые в рамках ГПД, включают:
      {
          "data" : {
              "time" : 1500793319499,
              "asks" : [
                  [1000, 0.5], [1001, 0.23], [1004, 2.1]
                  // ... 
              ],
              "bids" : [
                  [999, 0.25], [998, 0.8], [995, 1.4]
                  // ... 
              ]
          }
      }
      
  • GetTrades

    • Поле method: trades
    • Params поле:
      {"symbol":"eth_usdt"}
      
    • Услуги, предоставляемые в рамках ГПД, включают:
      { 
          "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

    • Поле method: хрестоматия хрестоматия
    • Params поле:
      {
          "limit":"500",
          "period":"60",          // 60分钟
          "symbol":"ETH_USDT"
      }
      
    • Услуги, предоставляемые в рамках ГПД, включают:
      {
          "data": [
                  // "Time":1500793319000,"Open":1.1,"High":2.2,"Low":3.3,"Close":4.4,"Volume":5.5
                  [1500793319, 1.1, 2.2, 3.3, 4.4, 5.5],
                  [1500793259, 1.01, 2.02, 3.03, 4.04, 5.05],
                  // ...
          ]
      }
      
  • GetMarketsОжидание

    • Поле method:""
    • Params поле:
      {}
      
    • Услуги, предоставляемые в рамках ГПД, включают:
      {}
      
  • GetTickersОжидание

    • Поле method:""
    • Params поле:
      {}
      
    • Услуги, предоставляемые в рамках ГПД, включают:
      {}
      
  • Получить учетную запись

    • Поле method: accounts
    • Params поле:
      {}
      
    • Услуги, предоставляемые в рамках ГПД, включают:
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • GetAssets

    • Поле method: вкладывает активы вкладывает
    • Params поле:
      {}
      
    • Услуги, предоставляемые в рамках ГПД, включают:
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"},
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • СоздатьЗаказ / Купить / Продать

    • Поле method: переходный переходный переходный
    • Params поле:
      {"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
      
    • Услуги, предоставляемые в рамках ГПД, включают:
      {
          "data": {
              "id": "BTC-USDT,123456"
          }
      }
      
  • GetOrders

    • Поле method: orders
    • Params поле:
      {"symbol":"ETH_USDT"}
      
    • Услуги, предоставляемые в рамках ГПД, включают:
      {
          "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"
              }, 
              // ...
          ]
      }
      
  • Получить Ордер

    • Поле method: order
    • Params поле:
      {
          "id":"ETH-USDT,123456",       // 策略中调用:exchange.GetOrder("ETH-USDT,123456")
          "symbol":"ETH_USDT"
      }
      
    • Услуги, предоставляемые в рамках ГПД, включают:
      { 
          "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
          }
      }
      
  • Заказы GetHistory

    • Поле method: "Historyorders"
    • Params поле:
      {"limit":0,"since":0,"symbol":"ETH_USDT"}
      
    • Услуги, предоставляемые в рамках ГПД, включают:
      {
          "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"
              }, 
              // ...
          ]
      }
      
  • Отменить заказ

    • Поле method: отменить
    • Params поле:
      {"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
      
    • Услуги, предоставляемые в рамках ГПД, включают:
      {
          "data": true    // 只要该JSON中没有error字段,都默认为撤单成功
      }
      
  • ИО

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

    // 策略实例中调用
    exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")
    
    • Поле method:"__api_/api/v5/trade/orders-pending"Содержимое поля method начинается с __api_, что означает, что это вызов к функции exchange.IO в примере политики.
    • Params поле:
      {"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
      
    • Услуги, предоставляемые в рамках ГПД, включают:
      {
          "data": {"code": "0", "data": [], "msg": ""}    // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
      }
      
  • Другие Функции API других платформ изобретателей, используемые в примерах стратегии, например:exchange.Go()exchange.GetRawJSON()Функции и т.д. не нуждаются в упаковке, способы вызова, функции неизменны.

Фьючерсные биржи

В дополнение к функциям, которые поддерживаются на всех насущных биржах, существуют некоторые API-функции, которые являются специфическими для фьючерсных бирж.

Ожидание

  • ПолучитеПозиции
  • Установка уровня границы
  • GetFundings

Примеры общих протоколов версии Python

REST общий протокольный блок подключается к интерфейсу REST API биржи OKX и упаковывается как объект биржи на месте. Реализация обертывания запросов и ответов с помощью общедоступного интерфейса. Осуществление закрытого интерфейса для подписи, запроса и ответа. В данном примере основное внимание уделяется тестированию обучения, а остальные интерфейсы используют данные амуляции, которые отвечают непосредственно хостеру для тестирования.

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

Больше