Tài nguyên đang được tải lên... tải...

Hướng dẫn truy cập vào giao dịch định lượng của nhà phát minh

Tác giả:Những nhà phát minh định lượng - những giấc mơ nhỏ, Tạo: 2024-10-29 14:37:56, Cập nhật: 2024-11-12 21:58:55

[TOC]

img

Nền tảng giao dịch định lượng của nhà phát minh hỗ trợ nhiều sàn giao dịch tiền điện tử và bao gồm các sàn giao dịch chính trên thị trường. Tuy nhiên, vẫn còn nhiều sàn giao dịch chưa được bao gồm, người dùng cần sử dụng các sàn giao dịch này có thể truy cập thông qua giao thức chung định lượng của nhà phát minh.RESTthỏa thuận hoặcFIXCác nền tảng của thỏa thuận cũng có thể được truy cập.

Bài viết này sẽRESTVí dụ như giao thức truy cập, giải thích cách sử dụng giao thức chung của nền tảng giao dịch định lượng của nhà phát minh để gói và truy cập vào OKX Exchange API.

  • Các quy trình làm việc của Hiệp định chung là: Quá trình yêu cầu: Các trường hợp chính sách chạy trên Trình quản lý -> Các thủ tục giao thức chung -> API sàn giao dịch Quá trình trả lời: API sàn giao dịch -> Chương trình giao thức chung -> Trường hợp chính sách chạy trên người quản lý

1, cấu hình sàn giao dịch

Các nhà phát minh định lượng nền tảng giao dịch cấu hình sàn giao dịch:

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

img

  • Chọn giao thức: Chọn "General Protocol".
  • Địa chỉ dịch vụ: GPRS về bản chất là một dịch vụ RPC Vì vậy, cần phải xác định rõ địa chỉ dịch vụ, cổng khi cấu hình sàn giao dịch.托管者上运行的策略实例 -> 通用协议程序Trong quá trình này, người quản lý biết nơi truy cập vào quy trình giao thức chung. Ví dụ:http://127.0.0.1:6666/OKXCác chương trình giao thức chung thường chạy trên cùng một thiết bị (máy chủ), vì vậy địa chỉ dịch vụ được viết vào máy chủ (localhost), cảng sử dụng một hệ thống không chiếm dụng.
  • Chìa khóa truy cập:托管者上运行的策略实例 -> 通用协议程序Trong quá trình này, thông tin cấu hình của sàn giao dịch được truyền đi.
  • Chìa khóa bí mật:托管者上运行的策略实例 -> 通用协议程序Trong quá trình này, thông tin cấu hình của sàn giao dịch được truyền đi.
  • Người dân Việt Nam: Các nhãn đối tượng sàn giao dịch trên nền tảng giao dịch định lượng của nhà phát minh được sử dụng để xác định một đối tượng sàn giao dịch.

Các trang web của các công ty khác cũng có thể tham khảo thông tin về các ứng dụng này.

img

Thông tin cấu hình khóa bí mật OKX:

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

2, triển khai trình quản trị và chương trình giao thức chung (plugin)

  • Người quản lý Bất kỳ chiến lược thực tế nào chạy trên nền tảng giao dịch định lượng của nhà phát minh đều phải triển khai một người quản lý, người quản lý triển khai cụ thể có thể tham khảo hướng dẫn nền tảng, không được mô tả ở đây.

  • 2, Ứng dụng giao thức chung (plugin) Người quản lý và giao thức chung thường được triển khai trên cùng một thiết bị, và các chương trình giao thức chung có thể sử dụng bất kỳ ngôn ngữ nào để viết thiết kế, và bài viết này được viết bằng Python 3. Tất nhiên, FMZ cũng hỗ trợ chạy các chương trình python, và cũng có thể sử dụng giao thức này như một ổ đĩa thực để cung cấp cho các nhà phát minh nền tảng giao dịch định lượng hỗ trợ truy cập API giao dịch chưa đóng gói. Sau khi chạy chương trình giao thức chung, bắt đầu nghe lén:http://127.0.0.1:6666Các chương trình giao thức chung có thể sử dụng các đường dẫn cụ thể, chẳng hạn như/OKXNhững người bị bắt giữ ở đây là những người bị bắt.

3, Trường hợp chính sách yêu cầu hàm API của FMZ

Khi gọi các hàm API của nền tảng (FMZ) trong chính sách, các chương trình giao thức chung sẽ nhận được các yêu cầu từ người quản lý. Các công cụ giải quyết của nền tảng cũng có thể được thử nghiệm, ví dụ:

Trang công cụ gỡ lỗi:

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

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

Gọi điệnexchange.GetTicker()Các chức năng, các chương trình giao thức chung nhận được yêu cầu:

POST /OKX HTTP/1.1 
{
    "access_key":"xxx",
    "method":"ticker",
    "nonce":1730275031047002000,
    "params":{"symbol":"LTC_USDT"},
    "secret_key":"xxx"
}
  • access_key: Chìa khóa bí mật giao dịch được cấu hình khi nền tảng "cấu hình giao dịch" trong bài viết trên
  • secret_key: Chìa khóa bí mật của sàn giao dịch được cấu hình trong bài viết trên khi nền tảng "thiết lập sàn giao dịch"
  • method: liên quan đến cuộc gọi giao diện trong chính sách, gọiexchange.GetTicker()Những người khác cũng có thể làm điều đó.methodĐó làticker
  • Nonce: Thời gian khi yêu cầu xảy ra.
  • Params: Các tham số liên quan đến cuộc gọi giao diện trong chính sách, ví dụ nhưexchange.GetTicker()Khi gọi, các tham số liên quan là:{"symbol":"LTC_USDT"}

4, Các quy trình giao thức chung truy cập giao diện giao dịch

Khi chương trình giao thức chung nhận được yêu cầu từ người quản lý, thông tin có thể được biết dựa trên thông tin được mang theo trong yêu cầu: các chức năng API nền tảng được yêu cầu (bao gồm thông tin tham số) và các khóa bí mật của sàn giao dịch.

Dựa trên thông tin này, chương trình giao thức chung có thể truy cập vào giao diện giao dịch, lấy dữ liệu hoặc thực hiện một số hoạt động.

Thông thường giao diện giao dịch có các phương pháp như GET / POST / PUT / DELETE, được chia thành giao diện công cộng và giao diện riêng tư.

  • Giao diện công cộng: không cần xác minh chữ ký, được yêu cầu trực tiếp trong quy trình giao thức chung.
  • Giao diện riêng: Giao diện cần xác minh chữ ký, cần thực hiện chữ ký trong quy trình giao thức chung, sau đó yêu cầu giao diện API của các sàn giao dịch này.

Các quy trình giao thức chung nhận được dữ liệu phản hồi giao diện giao dịch để xử lý thêm, tạo thành dữ liệu mà người quản lý mong đợi (được giới thiệu dưới đây). Cụ thể là OKX Exchange, một ứng dụng của lớp OKX trong mô hình giao thức chung Python.GetTickerGetAccountTương tự như vậy.

5. Các chương trình giao thức chung trả lời dữ liệu cho người quản lý

Khi một chương trình giao thức chung truy cập vào giao diện API của sàn giao dịch, thực hiện một số thao tác hoặc thu thập một số dữ liệu, nó cần phản hồi kết quả cho người quản lý.

Dữ liệu phản hồi cho người quản lý khác nhau tùy thuộc vào giao diện mà chính sách được gọi và được chia thành hai loại:

  • Một số người khác cũng cho biết rằng họ đang tìm kiếm một ứng dụng khác để gọi giao dịch.

    {
        "data": null,  // "data" can be of any type 
        "raw": null    // "raw" can be of any type 
    }
    
    • data: cấu trúc cụ thể của trường và các yêu cầu được nhận bởi trình giao thức chungmethodLiên quan, cấu trúc dữ liệu được sử dụng để xây dựng các hàm API nền tảng FMZ cuối cùng được trả về, sau đó sẽ có danh sách tất cả các giao diện.
    • raw: Quảng trường này có thể truyền dữ liệu nguyên bản của phản hồi API giao dịch, ví dụ:exchange.GetTicker()Tiến trình tick được trả về, được ghi trong Info field của tickrawCác trường vàdataMột số ứng dụng trên nền tảng không cần dữ liệu này.
  • Chương trình giao thức chung gọi giao diện giao dịch thất bại (lỗi kinh doanh, lỗi mạng, v.v.)

    {
        "error": ""    // "error" contains an error message as a string
    }
    
    • error: thông báo lỗi, sẽ được hiển thị trong nhật ký lỗi của các khu vực nhật ký trang như ổ đĩa thực của nền tảng FMZ, công cụ debugging.

Dữ liệu phản hồi giao thức chung của chương trình chiến lược trình bày:

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

img

6, Thỏa thuận cấu trúc dữ liệu trong giao thức chung

Những nội dung trên là một quá trình ngắn gọn của các chương trình giao thức chung tham gia vào việc truy cập (FMZ chưa được đóng gói) API sàn giao dịch, quá trình này chỉ giải thích các cuộc gọi trong công cụ debugging nền tảng (FMZ).exchange.GetTicker()Tiến trình trong khi chức năng. Tiếp theo sẽ được chi tiết chi tiết về các chi tiết tương tác của tất cả các chức năng API nền tảng.

Nền tảng bao bọc các chức năng chung của các sàn giao dịch, tất cả đều thống nhất thành một chức năng, chẳng hạn như chức năng GetTicker, yêu cầu thông tin thị trường hiện tại của một loại nào đó, về cơ bản là một API có sẵn cho tất cả các sàn giao dịch. Vì vậy, khi truy cập vào giao diện API được bao bọc trên nền tảng trong trường hợp chính sách, người quản lý sẽ gửi yêu cầu đến plugin "General Protocol" (được đề cập ở trên):

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

Khi gọi các hàm API được bọc bởi các nền tảng phát minh khác nhau trong chính sách (ví dụ như GetTicker), các nhà quản lý sẽ gửi các yêu cầu khác nhau đến giao thức chung.methodparams◎ Khi thiết kế giao thức chung, hãy dựa vàomethodCác nội dung của các giao diện này được sử dụng để thực hiện các hoạt động cụ thể. Dưới đây là tất cả các trường hợp yêu cầu - trả lời của giao diện.

Sàn giao dịch trực tiếp

Ví dụ, cặp giao dịch hiện tại là:ETH_USDT, sau đó không còn nói gì nữa. Dữ liệu mà người quản lý mong đợi giao thức tổng quát trả lời được viết chủ yếu trong trường dữ liệu, hoặc có thể thêm một trường raw ghi lại dữ liệu gốc của giao diện giao dịch.

  • GetTicker

    • method field: ticker
    • Các trường params:
      {"symbol":"ETH_USDT"}
      
    • Các nhà quản lý dự kiến sẽ trả lời với thông tin sau:
      {
          "data": {
              "symbol": "ETH_USDT",      // 对应GetTicker函数返回的Ticker结构中的Symbol字段
              "buy": "2922.18",          // ...对应Buy字段
              "sell": "2922.19", 
              "high": "2955", 
              "low": "2775.15", 
              "open": "2787.72", 
              "last": "2922.18", 
              "vol": "249400.888156", 
              "time": "1731028903911"
          },
          "raw": {}                      // 可以增加一个raw字段记录交易所API接口应答的原始数据
      }
      
  • GetDepth

    • method field: depth
    • Các trường params:
      {"limit":"30","symbol":"ETH_USDT"}
      
    • Các nhà quản lý dự kiến sẽ trả lời với thông tin sau:
      {
          "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 field: trades
    • Các trường params:
      {"symbol":"eth_usdt"}
      
    • Các nhà quản lý dự kiến sẽ trả lời với thông tin sau:
      { 
          "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 field: records
    • Các trường params:
      {
          "limit":"500",
          "period":"60",          // 60分钟
          "symbol":"ETH_USDT"
      }
      
    • Các nhà quản lý dự kiến sẽ trả lời với thông tin sau:
      {
          "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Đang được thực hiện

    • method: "
    • Các trường params:
      {}
      
    • Các nhà quản lý dự kiến sẽ trả lời với thông tin sau:
      {}
      
  • GetTickersĐang được thực hiện

    • method: "
    • Các trường params:
      {}
      
    • Các nhà quản lý dự kiến sẽ trả lời với thông tin sau:
      {}
      
  • GetAccount

    • method field: accounts
    • Các trường params:
      {}
      
    • Các nhà quản lý dự kiến sẽ trả lời với thông tin sau:
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • GetAssets

    • trường method: assets
    • Các trường params:
      {}
      
    • Các nhà quản lý dự kiến sẽ trả lời với thông tin sau:
      {
          "data": [
              {"currency": "TUSD", "free": "3000", "frozen": "0"},
              {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
              // ...
          ]
      }
      
  • CreateOrder / Mua / Bán

    • method field: trade
    • Các trường params:
      {"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
      
    • Các nhà quản lý dự kiến sẽ trả lời với thông tin sau:
      {
          "data": {
              "id": "BTC-USDT,123456"
          }
      }
      
  • GetOrders

    • method field: orders
    • Các trường params:
      {"symbol":"ETH_USDT"}
      
    • Các nhà quản lý dự kiến sẽ trả lời với thông tin sau:
      {
          "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 field: order
    • Các trường params:
      {
          "id":"ETH-USDT,123456",       // 策略中调用:exchange.GetOrder("ETH-USDT,123456")
          "symbol":"ETH_USDT"
      }
      
    • Các nhà quản lý dự kiến sẽ trả lời với thông tin sau:
      { 
          "data": {
              "id": "ETH-USDT,123456",
              "symbol": "ETH_USDT"
              "amount": 0.15,
              "price": 1002,
              "status": "pending",    // "pending", "pre-submitted", "submitting", "submitted", "partial-filled", "filled", "closed", "finished", "partial-canceled", "canceled"
              "deal_amount": 0,
              "type": "buy",          // "buy"、"sell"
              "avg_price": 0,         // 如果交易所没有提供,在处理时可以赋值为0
          }
      }
      
  • GetHistoryOrders

    • method field: historyorders
    • Các trường params:
      {"limit":0,"since":0,"symbol":"ETH_USDT"}
      
    • Các nhà quản lý dự kiến sẽ trả lời với thông tin sau:
      {
          "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"
              }, 
              // ...
          ]
      }
      
  • Hủy lệnh

    • method field: cancel
    • Các trường params:
      {"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
      
    • Các nhà quản lý dự kiến sẽ trả lời với thông tin sau:
      {
          "data": true    // 只要该JSON中没有error字段,都默认为撤单成功
      }
      
  • IO

    exchange.IO函数用于直接访问交易所接口,例如我们以GET /api/v5/trade/orders-pending, 参数:instType=SPOT,instId=ETH-USDTVí dụ:

    // 策略实例中调用
    exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT&instId=ETH-USDT")
    
    • method field:"__api_/api/v5/trade/orders-pending"Nội dung của trường method bắt đầu bằng __api_, cho thấy điều này được kích hoạt bởi cuộc gọi của hàm exchange.IO trong trường hợp chính sách.
    • Các trường params:
      {"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
      
    • Các nhà quản lý dự kiến sẽ trả lời với thông tin sau:
      {
          "data": {"code": "0", "data": [], "msg": ""}    // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
      }
      
  • Những người khác Các chức năng API nền tảng người phát minh khác được sử dụng trong các trường hợp chiến lược, ví dụ:exchange.Go()exchange.GetRawJSON()Các chức năng như không cần đóng gói, cách gọi, chức năng không thay đổi.

Sàn giao dịch tương lai

Ngoài việc hỗ trợ các chức năng của tất cả các sàn giao dịch trực tiếp, sàn giao dịch tương lai cũng có một số chức năng API riêng biệt của sàn giao dịch tương lai.

Đang được thực hiện

  • GetPositions
  • SetMarginLevel
  • GetFundings

Python phiên bản mô hình giao thức chung

REST General Protocol Plug truy cập OKX Exchange REST API, được đóng gói như một đối tượng giao dịch trực tiếp. Thực hiện một giao diện công cộng với dữ liệu yêu cầu và trả lời. Thực hiện một giao diện riêng để ký kết, yêu cầu và trả lời dữ liệu bao bì. Mô hình này chủ yếu là để thử nghiệm học tập, phần còn lại của giao diện sử dụng dữ liệu giả lập trả lời trực tiếp cho người quản lý để thử nghiệm.

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

Thêm nữa