리소스 로딩... 로딩...

FMZ 양자 거래 플랫폼 사용자 지정 프로토콜 액세스 가이드

저자:FMZ~리디아, 창작: 2024-11-08 16:37:25, 업데이트: 2024-11-15 10:23:15

img

우리는 FMZ 양자 거래 플랫폼은 많은 암호화폐 거래소를 지원하고 시장의 주류 거래소를 캡슐화합니다. 그러나 아직 캡슐화되지 않은 많은 거래소가 있습니다. 이러한 거래소를 사용해야하는 사용자는 FMZ 양자 사용자 지정 프로토콜을 통해 액세스 할 수 있습니다. 암호화폐 거래소에 국한되지 않고,REST프로토콜 또는FIX또한 프로토콜에 액세스 할 수 있습니다.

이 기사 는REST접근 프로토콜은 예를 들어 FMZ 퀀트 트레이딩 플랫폼의 사용자 지정 프로토콜을 사용하여 OKX 거래소의 API를 캡슐화하고 액세스하는 방법을 설명합니다. 달리 명시되지 않는 한이 기사는 REST 사용자 지정 프로토콜을 참조합니다.

  • 사용자 지정 프로토콜의 작업 흐름은 다음과 같습니다. 요청 프로세스: 도커에서 실행되는 전략 인스턴스 -> 사용자 지정 프로토콜 프로그램 -> 교환 API 응답 프로세스: 교환 API -> 사용자 지정 프로토콜 프로그램 -> 도커에서 실행되는 전략 인스턴스

1. 교환을 구성합니다

FMZ 양자 거래 플랫폼에서 교환을 구성하는 페이지:

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 양자 거래 플랫폼에서 특정 교환 대상을 식별하는 교환 대상의 태그.

문서에서 공개된 OKX 플러그인 구성의 스크린샷은 다음과 같습니다.

img

OKX 교환 비밀 키 구성 정보:

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

2. 도커 및 사용자 지정 프로토콜 프로그램 (플러그인) 의 배포

    1. 도커 FMZ 퀀트 트레이딩 플랫폼에서 모든 전략을 실행하려면 도커가 배포되어야 합니다. 도커를 배포하는 방법에 대한 자세한 내용은 플랫폼 튜토리얼을 참조하십시오. 여기에 세부 사항이 없습니다.
    1. 사용자 지정 프로토콜 프로그램 (플러그인) 도커와 사용자 지정 프로토콜은 일반적으로 동일한 장치에 배포됩니다. 사용자 지정 프로토콜 (서비스) 프로그램은 모든 언어로 작성 될 수 있습니다. 이 기사는 파이썬으로 작성되었습니다. 모든 파이썬 프로그램을 실행하는 것과 마찬가지로 직접 실행 할 수 있습니다 (예전에 파이썬 환경의 다양한 구성을 수행하십시오). 물론 FMZ는 파이썬 프로그램을 실행하는 것도 지원합니다. 또한 FMZ 양자 거래 플랫폼에 패키지화되지 않은 교환 API 액세스를 지원하기 위해 이 사용자 지정 프로토콜을 라이브 트레이딩으로 실행할 수도 있습니다. 사용자 지정 프로토콜 프로그램이 실행되면, 그것은 듣기 시작합니다:http://127.0.0.1:6666사용자 지정 프로토콜 프로그램은 특정 경로를 처리할 수 있습니다./OKX.

3. 전략 인스턴스 FMZ API 함수를 요청

전략에서 (FMZ) 플랫폼 API 함수가 호출되면 사용자 지정 프로토콜 프로그램은 도커로부터 요청을 받게 됩니다. 또한 플랫폼의 디버깅 도구를 사용하여 테스트 할 수 있습니다. 예를 들어:

디버깅 도구 페이지:

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: 위 플랫폼에서 구성된 교환 키
  • 방법: 전략에서 호출 인터페이스와 관련된, 호출 할 때exchange.GetTicker(), methodticker.
  • nonce: 요청이 발생한 시점.
  • parameters: 이 예제에서 전략에서 인터페이스 호출과 관련된 매개 변수, 호출할 때exchange.GetTicker(), 관련 매개 변수는 다음과 같습니다:{"symbol":"LTC_USDT"}.

4. 사용자 지정 프로토콜 프로그램 액세스 교환 인터페이스

사용자 지정 프로토콜 프로그램이 도커로부터 요청을 수신할 때, 요청에 포함된 정보에 기초하여 전략에 의해 요청된 플랫폼 API 함수 (패라미터 정보 포함) 와 같은 정보를 얻을 수 있다.

이 정보에 기초하여, 사용자 지정 프로토콜 프로그램은 교환 인터페이스에 액세스하여 필요한 데이터를 얻거나 특정 작업을 수행 할 수 있습니다.

일반적으로 교환 인터페이스는 공개 인터페이스와 개인 인터페이스로 나뉘는 GET/POST/PUT/DELETE와 같은 방법을 가지고 있습니다.

  • 공개 인터페이스: 서명 검증을 요구하지 않고 사용자 지정 프로토콜 프로그램에서 직접 요청되는 인터페이스.
  • 프라이빗 인터페이스: 서명 검증을 요구하는 인터페이스. 서명은 이러한 교환의 API 인터페이스를 요청하기 위해 사용자 지정 프로토콜 프로그램에 구현되어야합니다.

사용자 지정 프로토콜 프로그램은 교환 인터페이스에서 응답 데이터를 수신하고 더 나아가 처리하여 도커가 기대하는 데이터를 구성합니다.GetTicker, GetAccount그리고 파이썬 사용자 지정 프로토콜 예제에서 CustomProtocolOKX 클래스 구현에 있는 다른 함수.

5. 사용자 지정 프로토콜 프로그램은 도커에 데이터를 응답

커스텀 프로토콜 프로그램이 교환 API 인터페이스에 액세스하여 특정 작업을 수행하거나 특정 데이터를 얻으면 결과를 도커에 전달해야합니다.

도커에 다시 입력되는 데이터는 전략에 의해 호출되는 인터페이스에 따라 다양하며 먼저 두 가지 범주로 나습니다.

  • 사용자 지정 프로토콜 프로그램은 교환 인터페이스를 성공적으로 호출합니다.
{
    "data": null,  // "data" can be of any type 
    "raw": null    // "raw" can be of any type 
}

데이터: 이 필드의 특정 구조는method사용자 지정 프로토콜 프로그램에서 수신된 요청에서, 그리고 FMZ 플랫폼 API 함수로 최종적으로 반환된 데이터 구조를 구성하는 데 사용됩니다. 모든 인터페이스는 아래에 나열됩니다. raw: 이 필드는 교환 API 인터페이스 응답의 RAW 데이터를 전달하는 데 사용할 수 있습니다.exchange.GetTicker()틱커 구조의 정보 필드는raw현장 및data일부 플랫폼의 API 기능은 이 데이터를 필요로 하지 않습니다.

  • 사용자 지정 프로토콜 프로그램은 교환 인터페이스를 호출하지 못했습니다 (비즈니스 오류, 네트워크 오류 등)
{
    "error": ""    // "error" contains an error message as a string
}

오류: 오류 정보, 이는 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

6. 사용자 지정 프로토콜에서 데이터 구조 합의

위는 (FMZ unpackaged) 교환 API에 액세스하는 데 참여하는 사용자 지정 프로토콜 프로그램의 간단한 과정입니다. 이 과정은 단지 프로세스를 설명합니다.exchange.GetTicker()(FMZ) 플랫폼 디버깅 툴의 기능입니다. 다음은 모든 플랫폼 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"
}

다른 FMZ 플랫폼에서 캡슐화된 API 함수를 전략 (GetTicker와 같이) 으로 호출할 때, 사용자 지정 프로토콜로 도커가 보내는 요청 형식도 다를 것입니다. 몸의 데이터 (JSON) 는method그리고params. 사용자 지정 프로토콜을 설계할 때, 메소드의 내용에 따라 특정 작업을 수행합니다. 다음은 모든 인터페이스의 요청-대응 시나리오입니다.

현장 교환

예를 들어, 현재 거래 쌍은:ETH_USDT도커가 사용자 지정 프로토콜이 응답하기를 기대하는 데이터는 주로 데이터 필드에 기록되며 원시 필드도 추가하여 교환 인터페이스의 원래 데이터를 기록할 수 있습니다.

  • GetTicker

방법 필드: 티커 패람스 필드:

{"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
}
  • 깊이를 얻으십시오

방법 필드: depth 패람스 필드:

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

방법 필드: 거래 패람스 필드:

{"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시행될 것

방법 필드: 패람스 필드:

{}

사용자 지정 프로토콜 응답에서 도커가 기대하는 데이터:

{}
  • GetAccount를 사용하세요

방법 필드: 회계 패람스 필드:

{}

사용자 지정 프로토콜 응답에서 도커가 기대하는 데이터:

{
    "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"}, 
        // ...
    ]
}
  • CreateOrder / 구매 / 판매

방법 필드: 거래 패람스 필드:

{"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"
        }, 
        // ...
    ]
}
  • GetOrder

방법 필드: order 패람스 필드:

{
    "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.
}
  • IO

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
}
  • 다른 것 다른 FMZ 플랫폼 API 기능은 전략 예제에서 사용되며, 예를 들어:exchange.Go(), exchange.GetRawJSON()그리고 다른 함수는 캡슐화 할 필요가 없으며 호출 방법과 함수는 변하지 않습니다.

선물 거래소

포트 거래소의 모든 기능을 지원하는 것 외에도 미래에셋 거래소는 미래에셋 거래소에 고유한 일부 API 기능을 가지고 있습니다.

시행될 것

  • 위치 얻으세요
  • MarginLevel 설정
  • GetFundings

파이썬 버전의 사용자 지정 프로토콜 예제

REST 사용자 지정 프로토콜 - 점 교환 객체로 캡슐화된 OKX 교환 REST API 인터페이스에 액세스. 공개 인터페이스 요청 및 응답 데이터 캡슐화 구현 개인 인터페이스 서명, 요청 및 응답 데이터 캡슐화 구현. 이 예제는 주로 테스트 및 학습을 위해 사용된다. 다른 인터페이스는 테스트를 위해 도커에 직접 응답하기 위해 시뮬레이션 데이터를 사용합니다.

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

더 많은