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

FMZ Quant Trading Platform Руководство по доступу к пользовательскому протоколу

Автор:FMZ~Lydia, Создано: 2024-11-08 16:37:25, Обновлено: 2024-11-15 10:23:15

img

Мы FMZ Quant Trading Platform поддерживает многие криптовалютные биржи и инкапсулирует основные биржи на рынке. Тем не менее, все еще есть много бирж, которые не инкапсулированы. Для пользователей, которым нужно использовать эти биржи, они могут получить к ним доступ через FMZ Quant Custom Protocol.Отдыхпротокол илиФИКСПротокол также может быть доступен.

В этой статьеОтдыхпротокол доступа в качестве примера, чтобы объяснить, как использовать пользовательский протокол FMZ Quant Trading Platform для инкапсулирования и доступа к API биржи OKX. Если не указано иное, в этой статье упоминается пользовательский протокол REST.

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

1. Настроить обмен

Страница для настройки биржи на платформе FMZ Quant Trading:

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

img

  • Выберите протокол: Выберите Custom Protocol.
  • Адрес службы: программа пользовательского протокола по существу является службой RPC. Поэтому при настройке обмена необходимо четко указать адрес службы и порт. Таким образом, докер знает, где получить доступ к программе пользовательского протокола во время процессаStrategy instance running on the docker -> Custom protocol program- Да. Например:http://127.0.0.1:6666/OKX, программа пользовательского протокола и докер обычно запускаются на одном устройстве (сервере), поэтому адрес службы записывается как локальная машина (localhost), а порт может использоваться как порт, который не занят системой.
  • Ключ доступа: Информация о конфигурации обмена, передаваемая во время процессаStrategy instance running on the docker -> Custom protocol program.
  • Секретный ключ: Информация о конфигурации обмена, передаваемая во время процессаStrategy instance running on the docker -> Custom protocol program.
  • Тэг: Тег обменного объекта на платформе FMZ Quant Trading, используемый для идентификации определенного обменного объекта.

Скриншот конфигурации плагина OKX, описанный в статье, выглядит следующим образом:

img

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

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

2. Развертывание Docker и программы пользовательского протокола (плагин)

    1. Докер Докер должен быть развернут для выполнения любой стратегии на платформе FMZ Quant Trading. Для подробной информации о том, как развернуть докер, пожалуйста, ознакомьтесь с руководством по платформе.
    1. Программа пользовательского протокола (плагин) Docker и пользовательский протокол обычно развертываются на одном и том же устройстве. Программа пользовательского протокола (сервиса) может быть написана на любом языке. Эта статья написана на Python3. Как и запуск любой программы Python, вы можете выполнять ее напрямую (заранее выполнять различные конфигурации среды Python). Конечно, FMZ также поддерживает запуск программ Python, и вы также можете запустить этот пользовательский протокол в качестве живой торговли, чтобы предоставить FMZ Quant Trading Platform поддержку для не упакованного доступа к API обмена. После запуска программы пользовательского протокола, она начинает слушать:http://127.0.0.1:6666Программа пользовательского протокола может обрабатывать конкретные пути, такие как/OKX.

3. Запросы на стратегию FMZ API

При вызове функции 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: ключ обмена, настроенный на платформе Configure Exchange выше
  • secret_key: ключ обмена, настроенный на платформе Configure Exchange выше
  • метод: связанный с интерфейсом вызова в стратегии, при вызовеexchange.GetTicker(), methodэтоticker.
  • nonce: Время, когда запрос был отправлен.
  • параметры: параметры, связанные с вызовом интерфейса в стратегии, в этом примере, при вызовеexchange.GetTicker(), соответствующие параметры:{"symbol":"LTC_USDT"}.

4. Программный протокол пользователя Доступ к интерфейсу обмена

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

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

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

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

Программа пользовательского протокола получает данные ответа от интерфейса обмена и далее обрабатывает их для построения ожидаемых докером данных (описанных ниже).GetTicker, GetAccountи другие функции в реализации класса CustomProtocolOKX в примере пользовательского протокола Python.

5. Программа пользовательского протокола отвечает на данные докера

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

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

  • Программа пользовательского протокола успешно вызвала интерфейс обмена:
{
    "data": null,  // "data" can be of any type 
    "raw": null    // "raw" can be of any type 
}

Данные: Специфическая структура этого поля связана сmethodв запросе, полученном программой пользовательского протокола, и используется для построения структуры данных, окончательно возвращенной функцией API платформы FMZ. Все интерфейсы будут перечислены ниже. raw: Это поле может быть использовано для передачи в сырые данные ответа интерфейса API обмена, такие как структура TICKER, возвращеннаяexchange.GetTicker()Поле Info структуры Ticker записывает данныеrawПоле иdataНекоторые функции API платформы не требуют этих данных.

  • Программа пользовательского протокола не смогла вызвать интерфейс обмена (обменная ошибка, сетевая ошибка и т. д.)
{
    "error": ""    // "error" contains an error message as a string
}

error: информация об ошибке, которая будет отображаться в журнале ошибок в регистре платформы (FMZ) для торговли в режиме реального времени, инструмента отладки и других страниц.

Демонстрирует пользовательские протокольные данные ответа, полученные программой стратегии:

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

img

Соглашение о структуре данных в пользовательском протоколе

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

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

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

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

Спотный обмен

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

  • GetTicker

поле метода: ticker поле параметров:

{"symbol":"ETH_USDT"}

Данные, которые докер ожидает в ответе на пользовательский протокол:

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

поле метода: глубина поле параметров:

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

поле метода: trades поле параметров:

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

поле метода: записи поле параметров:

{
    "limit":"500",
    "period":"60",          // 60 minutes
    "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Внедрять

поле метода: поле параметров:

{}

Данные, которые докер ожидает в ответе на пользовательский протокол:

{}
  • GetTickersВнедрять

поле метода: поле параметров:

{}

Данные, которые докер ожидает в ответе на пользовательский протокол:

{}
  • Получить учетную запись

поле метода: счета поле параметров:

{}

Данные, которые докер ожидает в ответе на пользовательский протокол:

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

поле метода: активы поле параметров:

{}

Данные, которые докер ожидает в ответе на пользовательский протокол:

{
    "data": [
        {"currency": "TUSD", "free": "3000", "frozen": "0"},
        {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
        // ...
    ]
}
  • СоздатьЗаказ / Купить / Продать

поле метода: торговля поле параметров:

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

Данные, которые докер ожидает в ответе на пользовательский протокол:

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

Поле метода: заказы поле параметров:

{"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"
        }, 
        // ...
    ]
}
  • Получить Ордер

поле метода: порядка поле параметров:

{
    "id":"ETH-USDT,123456",       // Calling in the strategy: 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,         // If the exchange does not provide it, it can be assigned a value of 0 during processing.
    }
}
  • Заказы GetHistory

Поле метода: исторические заказы поле параметров:

{"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"
        }, 
        // ...
    ]
}
  • Отменить заказ

поле метода: отменить поле параметров:

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

Данные, которые докер ожидает в ответе на пользовательский протокол:

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

Вexchange.IOФункция используется для прямого доступа к интерфейсу обмена.GET /api/v5/trade/orders-pending, parameters: instType=SPOT, instId=ETH-USDTНапример.

// Called in the strategy instance
exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")

поле метода:"__api_/api/v5/trade/orders-pending", поле метода начинается с _Апи, что указывает на то, что это вызваноexchange.IOвызов функции в экземпляре стратегии. поле параметров:

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

Данные, которые докер ожидает в ответе на пользовательский протокол:

{
    "data": {"code": "0", "data": [], "msg": ""}    // The data attribute value is the data of the exchange API: GET /api/v5/trade/orders-pending response
}
  • Прочие Другие функции API платформы FMZ, используемые в примерах стратегии, такие как:exchange.Go(), exchange.GetRawJSON()и другие функции не должны быть инкапсулированы, а метод вызова и функция остаются неизменными.

Фьючерсная биржа

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

Внедрять

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

Пример пользовательского протокола в версии Python

REST Custom Protocol - доступ к интерфейсу 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'
        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()

Больше