4
tập trung vào
1076
Người theo dõi

Hướng dẫn truy cập giao thức chung của nền tảng giao dịch định lượng Inventor

Được tạo ra trong: 2024-10-29 14:37:56, cập nhật trên: 2024-11-12 21:58:55
comments   0
hits   207

[TOC]

Hướng dẫn truy cập giao thức chung của nền tảng giao dịch định lượng Inventor

Nền tảng giao dịch định lượng Inventor 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 thống trên thị trường. Tuy nhiên, vẫn còn nhiều sàn giao dịch chưa được đóng gói. Đối với người dùng cần sử dụng các sàn giao dịch này, họ có thể truy cập thông qua giao thức chung được định lượng bởi nhà phát minh. Không giới hạn ở các sàn giao dịch tiền điện tử, bất kỳRESTThỏa thuận hoặcFIXNền tảng của thỏa thuận cũng có thể được truy cập.

Bài viết này sẽRESTLấy quyền truy cập giao thức làm ví dụ, bài viết 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 Inventor để đóng gói và truy cập API của Sàn giao dịch OKX. Trừ khi có quy định khác, bài viết này đề cập đến giao thức chung REST.

  • Quy trình làm việc của giao thức chung là: Quy trình yêu cầu: Phiên bản chiến lược đang chạy trên người giám hộ -> Chương trình giao thức chung -> API Exchange Quy trình phản hồi: Exchange API -> Chương trình giao thức chung -> Phiên bản chiến lược đang chạy trên người giám hộ

1. Cấu hình trao đổi

Trang cấu hình trao đổi trên Nền tảng giao dịch định lượng Inventor:

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

Hướng dẫn truy cập giao thức chung của nền tảng giao dịch định lượng Inventor

  • Chọn giao thức: Chọn “Giao thức chung”.
  • Địa chỉ dịch vụ: Chương trình giao thức chung về cơ bản là một dịch vụ RPC Do đó, cần phải chỉ định rõ ràng địa chỉ dịch vụ và cổng khi cấu hình trao đổi. Vì vậy trong托管者上运行的策略实例 -> 通用协议程序Trong quá trình này, máy chủ biết nơi để truy cập vào chương trình giao thức chung. Ví dụ:http://127.0.0.1:6666/OKXThông thường, chương trình giao thức chung và máy chủ được chạy trên cùng một thiết bị (máy chủ), do đó địa chỉ dịch vụ được ghi là máy cục bộ (localhost) và cổng có thể là cổng không bị hệ thống chiếm dụng.
  • Access Key: 托管者上运行的策略实例 -> 通用协议程序Thông tin cấu hình trao đổi được truyền trong quá trình này.
  • Secret Key: 托管者上运行的策略实例 -> 通用协议程序Thông tin cấu hình trao đổi được truyền trong quá trình này.
  • Nhãn: Nhãn đối tượng trao đổi trên Nền tảng giao dịch định lượng Inventor được sử dụng để xác định một đối tượng trao đổi cụ thể.

Ảnh chụp màn hình cấu hình plug-in OKX được tiết lộ trong bài viết như sau:

Hướng dẫn truy cập giao thức chung của nền tảng giao dịch định lượng Inventor

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

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

2. Triển khai chương trình giám sát và giao thức chung (plugin)

    1. Chủ nhà Để chạy bất kỳ chiến lược thời gian thực nào trên Nền tảng giao dịch định lượng Inventor, bạn phải triển khai một người giám hộ. Để biết cách triển khai cụ thể của người giám hộ, vui lòng tham khảo hướng dẫn về nền tảng, sẽ không được nhắc lại ở đây.
    1. Chương trình giao thức chung (plugin) Máy chủ và giao thức chung thường được triển khai trên cùng một thiết bị. Chương trình giao thức chung (dịch vụ) có thể được viết bằng bất kỳ ngôn ngữ nào. Bài viết này được viết bằng Python3. Giống như chạy bất kỳ chương trình Python nào, bạn có thể thực thi trực tiếp (thực hiện nhiều cấu hình khác nhau cho môi trường Python trước). Tất nhiên, FMZ cũng hỗ trợ chạy các chương trình Python và giao thức chung này có thể chạy như một đĩa thực để cung cấp cho nền tảng giao dịch định lượng của nhà phát minh khả năng truy cập API trao đổi không đóng gói. Sau khi chương trình giao thức chung đang chạy, hãy bắt đầu giám sát:http://127.0.0.1:6666Trong chương trình giao thức chung, các đường dẫn cụ thể có thể được chỉ định, ví dụ/OKXđể được xử lý.

3. Yêu cầu trường hợp chiến lược chức năng API FMZ

Khi hàm API nền tảng (FMZ) được gọi trong chiến lược, Chương trình Giao thức Chung sẽ nhận được yêu cầu từ người giám hộ. Bạn cũng có thể kiểm tra bằng các công cụ gỡ lỗi của nền tảng, ví dụ:

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

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

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

Gọiexchange.GetTicker()Chức năng, 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: Khóa trao đổi được cấu hình trong nền tảng “Cấu hình Exchange” ở trên
  • secret_key: Khóa trao đổi được cấu hình trong nền tảng “Cấu hình trao đổi” ở trên
  • phương pháp: liên quan đến giao diện gọi trong chiến lược, gọiexchange.GetTicker()giờ,methodĐó làticker
  • nonce: Dấu thời gian khi yêu cầu xảy ra.
  • params: Các tham số liên quan đến lệnh gọi giao diện trong chính sách.exchange.GetTicker()Khi gọi, các tham số có liên quan là:{"symbol":"LTC_USDT"}

4. Chương trình giao thức chung truy cập vào giao diện trao đổi

Khi chương trình giao thức chung nhận được yêu cầu từ người giám hộ, nó có thể lấy thông tin như chức năng API nền tảng (bao gồm thông tin tham số) mà chiến lược yêu cầu, khóa trao đổi, v.v. dựa trên thông tin có trong yêu cầu.

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 trao đổi để lấy dữ liệu cần thiết hoặc thực hiện một số hoạt động nhất định.

Thông thường giao diện trao đổi có các phương thức như GET/POST/PUT/DELETE, được chia thành giao diện công khai và giao diện riêng tư.

  • Giao diện công khai: giao diện không yêu cầu xác minh chữ ký và được yêu cầu trực tiếp trong chương trình giao thức chung.
  • Giao diện riêng: giao diện yêu cầu xác minh chữ ký. Chữ ký cần được triển khai trong chương trình giao thức chung để yêu cầu giao diện API của các trao đổi này.

Chương trình giao thức chung nhận dữ liệu phản hồi từ giao diện trao đổi, xử lý thêm và xây dựng thành dữ liệu mà người giám hộ mong đợi (được mô tả bên dưới). Tham khảo OKX spot exchange, triển khai lớp CustomProtocolOKX trong ví dụ giao thức chung của PythonGetTickerGetAccountVà các chức năng khác.

5. Chương trình giao thức chung phản hồi dữ liệu cho người giám hộ

Khi 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ố hoạt động nhất định hoặc lấy dữ liệu nhất định, nó cần phản hồi kết quả cho bên giám sát.

Dữ liệu được phản hồi cho người giám hộ thay đổi tùy theo giao diện mà chiến lược gọi và trước tiên được chia thành hai loại:

  • Chương trình giao thức chung gọi thành công giao diện trao đổi:
  {
      "data": null,  // "data" can be of any type 
      "raw": null    // "raw" can be of any type 
  }
  • dữ liệu: Cấu trúc cụ thể của trường này giống với cấu trúc trong yêu cầu mà chương trình giao thức chung nhận được.methodSau đây là danh sách tất cả các giao diện được sử dụng để xây dựng cấu trúc dữ liệu được trả về bởi hàm API nền tảng FMZ.

  • Raw: Trường này có thể được sử dụng để truyền dữ liệu thô của phản hồi API trao đổi, ví dụexchange.GetTicker()Cấu trúc Ticker được trả về bởi hàm có thông tin sau được ghi lại trong trường Info của cấu trúc Ticker:rawCác cánh đồng vàdataDữ liệu của trường; một số hàm API nền tảng không cần dữ liệu này.

  • Chương trình giao thức chung không gọi được giao diện trao đổi (lỗi nghiệp vụ, lỗi mạng, v.v.)

  {
      "error": ""    // "error" contains an error message as a string
  }
  • lỗi: thông tin lỗi sẽ được hiển thị trong nhật ký lỗi ở vùng nhật ký của đĩa thực nền tảng (FMZ), công cụ gỡ lỗi và các trang khác.

Thể hiện dữ liệu phản hồi giao thức chung được chương trình chính sách nhận được:

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

Hướng dẫn truy cập giao thức chung của nền tảng giao dịch định lượng Inventor

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

Trên đây là một quy trình tóm tắt về cách chương trình giao thức chung tham gia vào việc truy cập API trao đổi (FMZ chưa đóng gói). Quy trình này chỉ giải thích cách gọi công cụ gỡ lỗi nền tảng (FMZ).exchange.GetTicker()Quá trình chức năng. Tiếp theo, thông tin chi tiết về tương tác của tất cả các chức năng API nền tảng sẽ được giải thích chi tiết.

Nền tảng này đóng gói các chức năng chung của nhiều sàn giao dịch khác nhau và hợp nhất chúng thành một chức năng nhất định, 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 sản phẩm nhất định. Về cơ bản, đây là API mà tất cả các sàn giao dịch đều có. Vì vậy, khi giao diện API được đóng gói trên nền tảng được truy cập trong phiên bản chiến lược, người giám hộ sẽ gửi yêu cầu đến plug-in “Universal Protocol” (đã đề 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 đóng gói của nhiều nền tảng phát minh khác nhau trong chiến lược (chẳng hạn như GetTicker), định dạng yêu cầu do người giám hộ gửi đến giao thức chung cũng sẽ khác nhau. Dữ liệu (JSON) trong phần thân chỉ khác nhau ởmethodparams. Khi thiết kế một giao thức chung,methodBạn có thể thực hiện các thao tác cụ thể dựa trên nội dung. Sau đây là các tình huống yêu cầu-phản hồi cho tất cả các giao diện.

Trao đổi giao ngay

Ví dụ, cặp giao dịch hiện tại là:ETH_USDTTôi sẽ không đi vào chi tiết sau. Dữ liệu mà người giám hộ mong đợi giao thức chung phản hồi chủ yếu được ghi vào trường dữ liệu và cũng có thể thêm một trường thô để ghi lại dữ liệu gốc của giao diện trao đổi.

  • GetTicker

    • trường phương pháp: “ticker”
    • trường params:
    {"symbol":"ETH_USDT"}
    
    • Dữ liệu mà máy chủ mong đợi trong phản hồi giao thức chung:
    {
        "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

    • trường phương pháp: “độ sâu”
    • trường params:
    {"limit":"30","symbol":"ETH_USDT"}
    
    • Dữ liệu mà máy chủ mong đợi trong phản hồi giao thức chung:
    {
        "data" : {
            "time" : 1500793319499,
            "asks" : [
                [1000, 0.5], [1001, 0.23], [1004, 2.1]
                // ... 
            ],
            "bids" : [
                [999, 0.25], [998, 0.8], [995, 1.4]
                // ... 
            ]
        }
    }
    
  • GetTrades

    • trường phương pháp: “giao dịch”
    • trường params:
    {"symbol":"eth_usdt"}
    
    • Dữ liệu mà máy chủ mong đợi trong phản hồi giao thức chung:
    { 
        "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

    • trường phương pháp: “bản ghi”
    • trường params:
    {
        "limit":"500",
        "period":"60",          // 60分钟
        "symbol":"ETH_USDT"
    }
    
    • Dữ liệu mà máy chủ mong đợi trong phản hồi giao thức chung:
    {
        "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 Để được thực hiện

    • trường phương pháp: “”
    • trường params:
    {}
    
    • Dữ liệu mà máy chủ mong đợi trong phản hồi giao thức chung:
    {}
    
  • GetTickers Để được thực hiện

    • trường phương pháp: “”
    • trường params:
    {}
    
    • Dữ liệu mà máy chủ mong đợi trong phản hồi giao thức chung:
    {}
    
  • GetAccount

    • trường phương pháp: “tài khoản”
    • trường params:
    {}
    
    • Dữ liệu mà máy chủ mong đợi trong phản hồi giao thức chung:
    {
        "data": [
            {"currency": "TUSD", "free": "3000", "frozen": "0"}, 
            {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
            // ...
        ]
    }
    
  • GetAssets

    • trường phương pháp: “tài sản”
    • trường params:
    {}
    
    • Dữ liệu mà máy chủ mong đợi trong phản hồi giao thức chung:
    {
        "data": [
            {"currency": "TUSD", "free": "3000", "frozen": "0"},
            {"currency": "BTC", "free": "0.2482982056277609", "frozen": "0"}, 
            // ...
        ]
    }
    
  • CreateOrder / Buy / Sell

    • trường phương pháp: “thương mại”
    • trường params:
    {"amount":"0.1","price":"1000","symbol":"BTC_USDT","type":"buy"}
    
    • Dữ liệu mà máy chủ mong đợi trong phản hồi giao thức chung:
    {
        "data": {
            "id": "BTC-USDT,123456"
        }
    }
    
  • GetOrders

    • trường phương pháp: “đơn hàng”
    • trường params:
    {"symbol":"ETH_USDT"}
    
    • Dữ liệu mà máy chủ mong đợi trong phản hồi giao thức chung:
    {
        "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

    • trường phương pháp: “order”
    • trường params:
    {
        "id":"ETH-USDT,123456",       // 策略中调用:exchange.GetOrder("ETH-USDT,123456")
        "symbol":"ETH_USDT"
    }
    
    • Dữ liệu mà máy chủ mong đợi trong phản hồi giao thức chung:
    { 
        "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

    • trường phương pháp: “historyorders”
    • trường params:
    {"limit":0,"since":0,"symbol":"ETH_USDT"}
    
    • Dữ liệu mà máy chủ mong đợi trong phản hồi giao thức chung:
    {
        "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"
            }, 
            // ...
        ]
    }
    
  • CancelOrder

    • trường phương pháp: “hủy bỏ”
    • trường params:
    {"id":"ETH-USDT,123456","symbol":"ETH_USDT"}
    
    • Dữ liệu mà máy chủ mong đợi trong phản hồi giao thức chung:
    {
        "data": true    // 只要该JSON中没有error字段,都默认为撤单成功
    }
    
  • IO

Hàm exchange.IO được sử dụng để truy cập trực tiếp vào giao diện trao đổi. Ví dụ, chúng ta sử dụngGET /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")
  • trường phương pháp:"__api_/api/v5/trade/orders-pending"Trường phương thức bắt đầu bằng _api, cho biết phương thức này được kích hoạt bởi lệnh gọi hàm exchange.IO trong phiên bản chiến lược.

  • trường params:

    {"instId":"ETH-USDT","instType":"SPOT"}   // instType=SPOT&instId=ETH-USDT编码的参数会被还原为JSON
    
  • Dữ liệu mà máy chủ mong đợi trong phản hồi giao thức chung:

    {
        "data": {"code": "0", "data": [], "msg": ""}    // data属性值为交易所API:GET /api/v5/trade/orders-pending 应答的数据
    }
    
  • khác Các hàm API khác của Inventor Platform được sử dụng trong các ví dụ về chiến lược, chẳng hạn như: exchange.Go()exchange.GetRawJSON()Các hàm như thế này không cần phải được đóng gói và phương thức gọi cũng như chức năng vẫn không thay đổi.

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

Ngoài việc hỗ trợ tất cả các chức năng của sàn giao dịch giao ngay, sàn giao dịch tương lai còn có một số chức năng API dành riêng cho sàn giao dịch tương lai.

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

  • GetPositions
  • SetMarginLevel
  • GetFundings

Phiên bản Python của ví dụ giao thức chung

Giao thức chung REST - truy cập vào giao diện REST API của sàn giao dịch OKX và đóng gói nó dưới dạng một đối tượng trao đổi tại chỗ. Đã triển khai giao diện chung cho việc đóng gói dữ liệu yêu cầu và phản hồi. Đã triển khai chữ ký giao diện riêng tư, đóng gói dữ liệu yêu cầu và phản hồi. Ví dụ này chủ yếu dùng để thử nghiệm và học hỏi. Các giao diện khác sử dụng dữ liệu mô phỏng để phản hồi trực tiếp tới máy chủ để 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()