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

발명가 양적 거래 플랫폼 일반 프로토콜 접근 지침

저자:발명가들의 수량화 - 작은 꿈, 창작: 2024-10-29 14:37:56, 업데이트: 2024-11-12 21:58:55

[TOC]

img

발명가 양적 거래 플랫폼은 다수의 암호화폐 거래소를 지원하고 있으며 시장에서 주류 거래소를 포괄하고 있다. 그러나 여전히 많은 거래소가 포괄되지 않고 있으며, 이러한 거래소를 사용해야 하는 사용자는 발명가 양적 일반 프로토콜을 통해 액세스할 수 있다.REST협약 또는FIX이 협약의 플랫폼은 또한 액세스 할 수 있습니다.

이 글은REST프로토콜 접근 예로, 발명자의 양적 거래 플랫폼의 일반 프로토콜을 사용하여 OKX 거래소의 API를 포괄하고 액세스하는 방법을 설명합니다. 이 문서에서는 특별한 설명이 없으면 REST 일반 프로토콜을 참조합니다.

  • GPA의 작업 프로세스는 다음과 같습니다. 요청 프로세스: 관리자에 실행되는 정책 예제 -> 일반 프로토콜 프로그램 -> 거래소 API 응답 프로세스: 거래소 API -> 일반 프로토콜 프로그램 -> 관리자에서 실행되는 정책 예제

1, 거래소를 구성합니다

발명가 양자 거래 플랫폼의 거래소 구성 페이지:

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

img

  • 프로토콜 선택: "일반 프로토콜"을 선택하십시오.
  • 서비스 주소: GPRS는 본질적으로 RPC 서비스입니다. 그래서 거래소를 구성할 때 서비스 주소, 포트를 명확하게 지정해야 합니다.托管者上运行的策略实例 -> 通用协议程序이 과정에서 관리자는 GPRS 절차에 액세스 할 수있는 곳을 알고 있습니다. 예를 들어:http://127.0.0.1:6666/OKX일반적으로 GPR프로그램은 호스트와 동일한 장치 (서버) 에서 실행되므로 서비스 주소는 로컬호스트로 쓰이고 포트는 시스템에서 채용되지 않은 포트를 사용할 수 있습니다.
  • 액세스 키:托管者上运行的策略实例 -> 通用协议程序이 과정에서 전달되는 거래소의 구성 정보는
  • 비밀 키:托管者上运行的策略实例 -> 通用协议程序이 과정에서 전달되는 거래소의 구성 정보는
  • 래리: 발명자의 양적 거래 플랫폼에서 거래소 객체 라그, 특정 거래소 객체를 식별하는 데 사용됩니다.

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

img

OKX 거래소의 비밀 키 설정 정보:

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

2. 호스트와 플러그인을 배포합니다

  • 1 관리자 발명자의 양적 거래 플랫폼에서 실행되는 모든 전략 디스크에는 관리자가 배포되어야 하며, 특정 배포 관리자는 플랫폼 설명서를 참조할 수 있습니다.

  • 2, 일반 프로토콜 프로그램 (plugin) 호스트와 GPR는 일반적으로 동일한 장치에 배포되며, GPR (서비스) 프로그램은 모든 언어를 사용하여 디자인을 작성할 수 있으며, 이 문서는 Python3를 사용하여 작성됩니다. 그리고 모든 Python 프로그램을 실행하는 것처럼 직접 실행 할 수 있습니다. 물론 FMZ에서도 파이썬 프로그램을 실행할 수 있는 지원이 있고, 이 프로토콜을 실제 디스크처럼 실행하여 발명자에게 양적 거래 플랫폼에 대한 공개된 거래소 API 접근을 지원할 수 있다. 이 자막은 이 모든 것을 통해 우리가 무엇을 할 수 있는지에 대한 것입니다.http://127.0.0.1:6666예를 들어, 특정 경로에 대해/OKX이 글은 이쪽에서 읽었습니다.

3, FMZ의 API 함수를 요청하는 정책 예제

FMZ 플랫폼의 API 함수를 정책에서 호출할 때, GP 프로그램은 호스트로부터 요청을 받는다. 플랫폼의 디뷰팅 도구를 사용하여 테스트 할 수도 있습니다. 예를 들어:

디버깅 도구 페이지:

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 함수 (변수 정보를 포함한) 또는 거래소 비밀 키 등의 정보를 알 수 있다.

이 정보에 따라, GPR프로그램은 거래소 인터페이스에 접속하여 필요한 데이터를 얻거나 특정 작업을 수행할 수 있습니다.

보통 거래소 인터페이스에는GET/POST/PUT/DELETE와 같은 방법이 있으며, 공공 인터페이스와 개인 인터페이스로 나다.

  • 공용 인터페이스: 서명 검증이 필요하지 않은 인터페이스, 일반 프로토콜 절차에서 직접 요청하십시오.
  • 개인 인터페이스: 서명 검증을 필요로 하는 인터페이스, 서명을 구현해야 하는 일반 프로토콜 프로세스, 그리고 API 인터페이스를 요청한다.

일반 프로토콜 절차는 거래소 인터페이스 응답 데이터를 수신하고, 추가 처리하여, 유탁자가 기대하는 데이터를 구성합니다. 참고로 OKX 현금 거래소, Python 일반 프로토콜 예제에서 CustomProtocolOKX 클래스 구현GetTickerGetAccount정수 함수입니다.

5. 일반 프로토콜 프로세스가 데이터를 관리자에게 응답합니다.

일반 프로토콜 프로그램이 거래소의 API 인터페이스를 접속하여 특정 동작을 수행하거나 특정 데이터를 획득한 후, 그 결과를 기탁자에게 피드백해야 합니다.

호스팅에 대한 피드백 데이터는 정책 호출의 인터페이스에 따라 다릅니다.

  • 유니폼을 입고 있는 유니폼을 입고 있는 유니폼을 입고 있는 유니폼

    {
        "data": null,  // "data" can be of any type 
        "raw": null    // "raw" can be of any type 
    }
    
    • data: 이 필드의 구체적인 구조와 GPRS가 수신한 요청에 대한method관련, FMZ 플랫폼 API 함수의 최종 반환을 구성하는 데이터 구조에 대한 모든 인터페이스 목록이 아래에 있습니다.
    • raw: 이 필드는 거래소 API 응답의 원본 데이터를 전송할 수 있습니다.exchange.GetTicker()이 함수에서 반환하는 틱커 구조는 틱커 구조의 정보 필드에 기록된raw필드 및data필드의 데이터; 플랫폼의 API 함수, 일부에는이 데이터가 필요하지 않습니다.
  • 일반 프로토콜 프로그램 호출 거래소 인터페이스 실패 (사업 오류, 네트워크 오류 등)

    {
        "error": ""    // "error" contains an error message as a string
    }
    
    • error: FMZ 플랫폼 디스크, 디버깅 도구 등 페이지 로그 영역의 오류 로그에서 표시되는 오류 정보.

이 프로그램은 GPRS 응답 데이터에 대해 설명합니다.

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

img

6. 일반 프로토콜의 데이터 구조 협약

위의 내용은 일반 프로토콜 프로그램이 액세스 (FMZ unwrapped) 거래소 API에 참여하는 간단한 과정입니다. 이 과정은 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) 호스트가 UTP에 보내는 요청 형식도 다를 수 있습니다.method그리고params◎ 일반 프로토콜을 설계할 때,method이 문서는 모든 인터페이스의 요청-응답 시나리오를 보여줍니다.

현금 거래소

예를 들어, 현재 거래 쌍은 다음과 같습니다.ETH_USDT, 이후 더 이상 설명하지 않습니다. 관리자는 GPTP 응답을 기대하는 데이터가 주로 데이터 필드에 기록되며, 원본 데이터를 기록하는 RAW 필드를 추가할 수도 있습니다.

  • GetTicker

    • method 필드: ticker
    • 파람스 필드:
      {"symbol":"ETH_USDT"}
      
    • 관리자는 GPA에 응답할 것으로 기대하는 데이터입니다.
      {
          "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
    • 파람스 필드:
      {"limit":"30","symbol":"ETH_USDT"}
      
    • 관리자는 GPA에 응답할 것으로 기대하는 데이터입니다.
      {
          "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??
    • 파람스 필드:
      {"symbol":"eth_usdt"}
      
    • 관리자는 GPA에 응답할 것으로 기대하는 데이터입니다.
      { 
          "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 필드: records
    • 파람스 필드:
      {
          "limit":"500",
          "period":"60",          // 60分钟
          "symbol":"ETH_USDT"
      }
      
    • 관리자는 GPA에 응답할 것으로 기대하는 데이터입니다.
      {
          "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실현될 것

    • 메소드 필드:""
    • 파람스 필드:
      {}
      
    • 관리자는 GPA에 응답할 것으로 기대하는 데이터입니다.
      {}
      
  • GetTickers실현될 것

    • 메소드 필드:""
    • 파람스 필드:
      {}
      
    • 관리자는 GPA에 응답할 것으로 기대하는 데이터입니다.
      {}
      
  • GetAccount를 사용하세요

    • method 필드: accounts
    • 파람스 필드:
      {}
      
    • 관리자는 GPA에 응답할 것으로 기대하는 데이터입니다.
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • GetAssets

    • method 필드: assets
    • 파람스 필드:
      {}
      
    • 관리자는 GPA에 응답할 것으로 기대하는 데이터입니다.
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"},
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • CreateOrder / 구매 / 판매

    • method 필드: trade
    • 파람스 필드:
      {"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
      
    • 관리자는 GPA에 응답할 것으로 기대하는 데이터입니다.
      {
          "data": {
              "id": "BTC-USDT,123456"
          }
      }
      
  • GetOrders

    • method 필드: orders
    • 파람스 필드:
      {"symbol":"ETH_USDT"}
      
    • 관리자는 GPA에 응답할 것으로 기대하는 데이터입니다.
      {
          "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

    • method 필드: order
    • 파람스 필드:
      {
          "id":"ETH-USDT,123456",       // 策略中调用:exchange.GetOrder("ETH-USDT,123456")
          "symbol":"ETH_USDT"
      }
      
    • 관리자는 GPA에 응답할 것으로 기대하는 데이터입니다.
      { 
          "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
    • 파람스 필드:
      {"limit":0,"since":0,"symbol":"ETH_USDT"}
      
    • 관리자는 GPA에 응답할 것으로 기대하는 데이터입니다.
      {
          "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 필드: cancel
    • 파람스 필드:
      {"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
      
    • 관리자는 GPA에 응답할 것으로 기대하는 데이터입니다.
      {
          "data": true    // 只要该JSON中没有error字段,都默认为撤单成功
      }
      
  • IO

    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")
    
    • 메소드 필드:"__api_/api/v5/trade/orders-pending"메소드 필드의 내용은 __api_로 시작되며, 이는 정책 인스턴스의 exchange.IO 함수 호출에 의해 촉발되었다는 것을 의미합니다.
    • 파람스 필드:
      {"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
      
    • 관리자는 GPA에 응답할 것으로 기대하는 데이터입니다.
      {
          "data": {"code": "0", "data": [], "msg": ""}    // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
      }
      
  • 다른 것 전략 예제에서 사용되는 다른 발명자 플랫폼 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'
        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()

더 많은