FMZ Quant Trading Platform Custom Protocol Truy cập vào các sàn giao dịch tùy chỉnh

Tác giả:Lydia., Tạo: 2023-07-12 15:29:54, Cập nhật: 2024-01-03 21:01:07

img

FMZ Quant Trading Platform Custom Protocol Truy cập vào các sàn giao dịch tùy chỉnh

Tài liệu sử dụng giao thức tùy chỉnh

Bạn có thể sử dụng giao thức chung này để truy cập bất kỳ trao đổi nào cung cấp giao dịch API, giao thức API cụ thể không bị giới hạn, cho dù đó là nghỉ ngơi, websocket, sửa chữa... Tất cả có thể được truy cập để sử dụng. Ví dụ giao thức tùy chỉnh Python:https://www.fmz.com/strategy/101399

1. tùy chỉnh giao thức plug-in hoạt động, thiết lập cổng

Cài đặt cho địa chỉ nghe và cổng của Custom Protocol Plugin có thể như sau: Ví dụ:http://127.0.0.1:6666/DigitalAssethoặchttp://127.0.0.1:6666/exchange.

Tại sao chúng ta cần thiết lập nhữngIPđường điKhông sao đâu? Đó là vì khithêm trao đổitrang trongFMZ Quant Platform Dashboard, chọn tùy chọn Custom Protocol hiển thị một Service Address ngoàiAPI-KEY. Địa chỉ dịch vụ này thông báo cho docker nơi truy cập IP và cổng (docker và chương trình plugin Custom Protocol có thể không chạy trên cùng một thiết bị). Ví dụ, địa chỉ dịch vụ có thể được điền nhưhttp://127.0.0.1:6666/DigitalAsset. DigitalAssetchỉ là một ví dụ và có thể được thay thế bằng một tên bạn chọn.

Trên trang Add Exchange của nền tảng FMZ Quant, thông thường cấu hình trao đổi chỉ yêu cầuaccess keysecret key. Tuy nhiên, một số giao dịch giao diện API yêu cầu mật khẩu giao dịch được truyền (ví dụ, giao diện đặt hàng của một số giao dịch). Trong những trường hợp như vậy, vì trang Custom Protocol không có điều khiển bổ sung để nhập thông tin này, chúng tôi có thể bao gồm các thông tin cấu hình cần thiết thêm trongsecret key(hoặcaccess keysau đó, trong chương trình Custom Protocol plugin, chúng ta có thể thực hiện một chuỗisplitHoạt động để tách các dữ liệu này, như được hiển thị trong hình mẫu.

img

Và sau đó trong plugin, xử lý nó để có đượcXXX_PassWord. Ví dụ, trong ví dụ hoàn chỉnh ở cuối bài viết này, trong hàm newBitgo:

func newBitgo(accessKey, secretKey string) *iBitgo {
    s := new(iBitgo)
    s.accessKey = accessKey
    s.secretKey = secretKey
    // Additional configuration information in the secretKey can be separated here and can be written as in the following comment
    /*
    arr := strings.SplitN(secretKey, ",", 2)
    if len(arr) != 2 {
        panic("Configuration error!")
    }
    s.secretKey = arr[0]   // secret key 
    s.passWord = arr[1]    // XXX_PassWord
    */
    s.apiBase = "https://www.bitgo.cn"
    s.timeout = 20 * time.Second
    s.timeLocation = time.FixedZone("Asia/Shanghai", 8*60*60)

    return s
}

Phân tích các tham số trong dữ liệu yêu cầu một lần nữa, dockers gửi dữ liệu yêu cầu như sau:

"secret_key" : "XXX",

Plugin nhận được dữ liệu đến có chứa loại thông tin này và tách XXX_PassWord khỏi nó dựa trên phân tách dấu phẩy, để nó có được dữ liệu được truyền thêm.

Ví dụ về plugin giao thức tùy chỉnh tổng thểmainchức năng:GoMô tả ngôn ngữ:

func main(){
    var addr = flag.String("b", "127.0.0.1:6666", "bing addr")   // Set command line parameters, default value description, port setting 6666
    flag.Parse()                                                 // Parsing the command line
    if *addr == "" {
        flag.Usage()                                             // Display the command line description
        return 
    }
    basePath := "/DigitalAsset"
    log.Println("Running ", fmt.Sprintf("http://%s%s", *addr, basePath), "...")   // Print listening port information
    http.HandleFunc(basePath, OnPost)
    http.ListenAndServe(*addr, nil)
}

2. Chức năng phản ứng

Chương trình Custom Protocol Plugin liên tục lắng nghe trên một cổng được chỉ định chorequestMột khi một yêu cầu được nhận, nó gọi chức năng phản hồi để thực hiện và sau đó phân tích các tham số từ dữ liệu yêu cầu.

/* JSON structure of request, FMZ Quant call GetTicker, docker sent to the custom protocol plugin case example (the value of params may be different for each API call, here the method is ticker):
{
    "access_key" : "XXX",               // `json:"access_key"`
    "secret_key" : "XXX",               // `json:"secret_key"`
    "nonce" : "1502345965295519602",    // `json:"nonce"`
    "method" : "ticker",                // `json:"method"`
    "params" : {                        // `json:"params"`
                   "symbol" : "btc",
                   ...
               },                       // The parameters are slightly different for each request. That is, different FMZ Quant APIs are called in the strategy with different parameters, which are described in the following sections for each API.
}
*/

Vì vậy, dựa trênMethodlĩnh vực trongrequestcấu trúc thu được bởi JSON deserializing yêu cầu Cơ thể dữ liệu nhận được trong chương trình Universal Protocol Plugin, chúng ta có thể sử dụng mộtswitchtuyên bố để phân loại và xử lý các API FMZ Quant khác nhau được gọi trên docker (tức là xác định FMZ Quant nàoAPIchiến lược chạy trên dock đang gọi):

Ví dụ trongGongôn ngữ:

switch request.Method {    // M of request.Method is capitalized here, the body of the request received by the custom protocol program for the JSON data, in the Go language, the anti-JSON serialization (Unmarshal) is a structure, the first letter of the field must be capitalized
  case "accounts" :        // When the exchange.GetAccount() function is called in the bot strategy on the docker, the docker sends in a request where the Body carries data with a method attribute value of accounts
      data, err = e.GetAccount(symbol)
  case "ticker" :
      data, err = e.GetTicker(symbol)
  case "depth" :
      data, err = e.GetDepth(symbol)
  case "trades" :
      data, err = e.GetTrades(symbol)
  case "trade" :
  ...
  default:
      ...

Sau khi thực hiện các nhánh này, dữ liệu được trả về nên được ghi vào cấu trúc mà chương trình Custom Protocol Plugin sẽ sử dụng để đáp ứng yêu cầu của docker.

Ví dụ trong ngôn ngữ Go:

defer func(){                                // Handling of closing work 
      if e := recover(); e != nil {          // The recover() function is used to catch panic, e ! = nil, i.e. an error has occurred
          if ee, ok := e.(error); ok {       // Type derivation, successful derivation assigns ee.Error() to e
              e = ee.Error()                 // Call the Error method to get the returned error string
          }
          ret = map[string]string{"error": fmt.Sprintf("%v", e)}
      }
      b, _ := json.Marshal(ret)              // Encode the result obtained from this call, ret, assign it to b, and write it into the response pointer
      w.Write(b)
      //fmt.Println("after w.Write the b is", string(b))     // test
  }()

3. Các loại cuộc gọi API

Xấp xỉ được chia thành hai loại:

  1. giao diện công khai không yêu cầu chữ ký, ví dụ:

GetTicker()

GetDepth()

GetTrades()

GetRecords(period)

  1. giao diện người dùng cần phải ký tên, chẳng hạn như:

Buy, Sell

GetOrder(id)

GetOrders()

GetAccount()

CancelOrder(id)... Phương pháp ký hiệu có thể khác nhau từ trao đổi sang trao đổi, và cần phải được viết cụ thể theo nhu cầu.

4. Định dạng dữ liệu cho sự tương tác giữa Custom Protocol Plugin và docker khi gọi các giao diện API FMZ Quant khác nhau:

Một số giao diện API FMZ Quant, chẳng hạn nhưGetName(), GetLabel(), vv, không gửi yêu cầu đếnPlugin giao thức tùy chỉnhkhi được gọi. Khi gọiexchange.GetName(), trao đổi được cấu hình trong plugin phổ quát sẽ trả về Exchange.

    1. GetTicker: Được sử dụng để lấy dữ liệu ticker hiện tại.

Cácmethodtrongrequestđược gửi bởiDockerđến chức năng phản hồi nghe làticker.

Docker gửi tham số:request.Params.symbol, được gửi bởi docker dựa trên tiền tệ được đặt trên trang của robot.

Định dạng dữ liệu (JSON) được mang trong cơ thể yêu cầu khi docker yêu cầu Custom Protocol Plugin

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "ticker",
    "params" :     {"symbol" : "ETH_BTC"},     // Take the ETH_BTC trading pair for example
}

Cấu trúc của giá trị trả về cuối cùng được gửi đến docker: (tức là định dạng mà dữ liệu được trả lại cho docker sau khi giao diện trao đổi được yêu cầu bởi plugin giao thức chung)

Cấu trúc JSON

{
    "data": {
        "time": 1500793319499,  // Millisecond timestamp, integer
        "buy": 1000,            // floating-point type as follows
        "sell": 1001,
        "last": 1005,
        "high": 1100,
        "low": 980,
        "vol": 523,
    }
}
    1. GetRecords: Được sử dụng để lấy dữ liệu đường K được cung cấp bởi trao đổi. (Dựa trên các tham số được yêu cầu bởi docker)

Cácmethodtrongrequestđược gửi bởi docker để nghe chức năng phản hồi làrecords.

Docker gửi các thông số:request.Params.period, liên quan đến các tham số đầu tiên củaexchange.GetRecordschức năng.request.Params.periodđại diện cho khoảng thời gian trong phút.60*24, đó là1440. request.Params.symbolđược gửi bởi docker dựa trên tiền tệ đặt.

Định dạng dữ liệu được mang trong cơ thể yêu cầu khi docker yêu cầu Custom Protocol Plugin.

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "records",
    "params" :     {"symbol" : "ETH_BTC", "period" : "1440"},     // Example of an ETH_BTC pair with a daily K-period
}

Cấu trúc của giá trị trả về cuối cùng được gửi đến docker:

Cấu trúc JSON

{
    "data": [
            [1500793319, 1.1, 2.2, 3.3, 4.4, 5.5],         // "Time":1500793319000,"Open":1.1,"High":2.2,"Low":3.3,"Close":4.4,"Volume":5.5
            [1500793259, 1.01, 2.02, 3.03, 4.04, 5.05],
            ...
    ]
}

Dữ liệu kiểm tra ngôn ngữ:

ret_records = []interface{}{
    [6]interface{}{1500793319, 1.1, 2.2, 3.3, 4.4, 5.5}, 
    [6]interface{}{1500793259, 1.01, 2.02, 3.03, 4.04, 5.05}
}

FMZ Quant PlatformLogmàn hìnhrecordsdữ liệu:

[
    {"Time":1500793319000,"Open":1.1,"High":2.2,"Low":3.3,"Close":4.4,"Volume":5.5},
    {"Time":1500793259000,"Open":1.01,"High":2.02,"Low":3.03,"Close":4.04,"Volume":5.05}
]

Lưu ý: 1. phần tử đầu tiên trong mảng hai chiều là kiểuintDocker sẽ tự động nhân con dấu thời gian bằng 1000, như đã đề cập ở trên.

    1. GetDepth: Lấy lại thông tin chiều sâu (sách đặt hàng, ask1, ask2... bid1, bid2...) từ sàn giao dịch.

Cácmethodtrongrequestđược gửi bởi docker để nghe chức năng phản hồi làdepth.

Docker gửi tham số:request.Params.symbol, được gửi bởi docker dựa trên tiền tệ được đặt trong chiến lược.

Định dạng dữ liệu được mang trong cơ thể yêu cầu khi docker yêu cầu Custom Protocol Plugin

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "depth",
    "params" :     {"symbol" : "ETH_BTC"},     // Take the ETH_BTC trading pair for example
}

Cấu trúc của giá trị trả về cuối cùng được gửi đến docker:

Cấu trúc JSON

{
    "data" : {
        "time" : 1500793319499,
        "asks" : [ [1000, 0.5], [1001, 0.23], [1004, 2.1], ... ],
        "bids" : [ [999, 0.25], [998, 0.8], [995, 1.4], ... ],
    }
}
    1. GetTrades: Nhận hồ sơ giao dịch của toàn bộ sàn giao dịch trong một khoảng thời gian nhất định (không bao gồm giao dịch của riêng bạn)

Cácmethodtrongrequestđược gửi bởi docker đến chức năng trả lời nghe là:trades.

Các thông số được gửi bởi docker: Giá trị củarequest.Params.symbollà tiền tệ giao dịch, ví dụ:btc, được gửi bởi docker dựa trên các thiết lập chiến lược.

Các định dạng dữ liệu được mang theo bởi docker khi yêu cầu các plugin giao thức tùy chỉnh là như sau:

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "trades",
    "params" :     {"symbol" : "ETH_BTC"},     // Take the ETH_BTC trading pair for example
}

Cấu trúc của giá trị trả về cuối cùng được gửi đến docker:

Cấu trúc JSON

{ 
    "data": [
        {
            "id": 12232153,
            "time" : 1529919412968,
            "price": 1000,
            "amount": 0.5,
            "type": "buy",             // "buy"、"sell"
        },{
            "id": 12545664,
            "time" : 1529919412900,
            "price": 1001,
            "amount": 1,
            "type": "sell",
        },{
            ...
        }
    ]
}
    1. Nhận tài khoản: Nhận thông tin tài sản tài khoản.

Cácmethodtrongrequestđược gửi bởi docker đến chức năng trả lời nghe là:accounts.

Các thông số được gửi bởi docker: (Lưu ý: Nói chung, nó là để có được tất cả các tài sản của tài khoản! Xin vui lòng tham khảo giao diện trao đổi để xem nếu nó là để có được các tài sản cá nhân hoặc tổng thông tin tài sản)

Các định dạng dữ liệu được mang theo bởi docker khi yêu cầu các plugin giao thức tùy chỉnh là như sau:

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "accounts",
    "params" :     {},                         
}

Cấu trúc của giá trị trả về cuối cùng được gửi đến docker:

Cấu trúc JSON

{
    "data": [
        {"currency": "btc", "free": 1.2, "frozen": 0.1},
        {"currency": "ltc", "free": 25, "frozen": 2.1},
        {"currency": "ltc", "free": 25, "frozen": 2.1},
        ...
    ],
    "raw" : {...}             // It is possible to write the raw message (response) returned by the exchange when the plugin accesses the exchange
}
    1. Mua, bán: Đặt lệnh giao dịch (đơn đặt hàng thị trường hoặc lệnh giới hạn).

Cácmethodtrongrequestđược gửi bởi docker đến chức năng trả lời nghe là:trade.

Các tham số được gửi bởi docker:request.Params.type: được gửi bởi docker dựa trên việc nó đang gọiexchange.Buyhoặcexchange.Sell, request.Params.price: tham số đầu tiên củaAPIchức năng được gọi trong chiến lược,request.Params.amount: tham số thứ hai củaAPIchức năng được gọi trong chiến lược,request.Params.symbol: được gửi bởi docker dựa trên tiền tệ giao dịch được thiết lập.

Các định dạng dữ liệu được mang theo bởi docker khi yêu cầu các plugin giao thức tùy chỉnh là như sau:

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "trade",
    "params" :     {
                       "symbol" : "ETH_BTC", 
                       "type" : "buy", 
                       "price" : "1000", 
                       "amount" : "1"
                   },                          // Example of an ETH_BTC trading pair, "type": "buy" buy request, price 1000, quantity 1
}

Cấu trúc của giá trị trả về cuối cùng được gửi đến docker:

Cấu trúc JSON

{
    "data": {
        "id": 125456,      // Order id returned after placing an order
                           // If the order id is in the form of a string like "asdf346sfasf532"
                           // Here the id can also be a string type
    }
}
    1. GetOrder: Nhận thông tin về một đơn đặt hàng cụ thể theo ID đơn đặt hàng

Cácmethodtrongrequestđược gửi bởi docker đến chức năng trả lời nghe là:order.

Các tham số được gửi bởi docker:request.Params.id, request.Params.symbol.

Các định dạng dữ liệu được mang theo bởi docker khi yêu cầu các plugin giao thức tùy chỉnh là như sau:

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "order",
    "params" :     {"symbol" : "ETH_BTC", "id" : "XXX"},     // Take the ETH_BTC trading pair and order ID XXX as an example. Please note that some exchanges use numerical order IDs, such as 123456, while others use string order IDs, such as poimd55sdfheqxv. The specific format of the order ID depends on the exchange.
}

Cấu trúc của giá trị trả về cuối cùng được gửi đến docker:

Cấu trúc JSON

{ 
    "data": {
        "id": 2565244,
        "amount": 0.15,
        "price": 1002,
        "status": "open",    // "open": pending, "closed": closed, "canceled": canceled
        "deal_amount": 0,
        "type": "buy",       // "buy"、"sell"
        "avg_price": 0,      // If not provided by the exchange, it can be assigned a value of 0 during processing
    }
}
    1. GetOrders: Nhận thông tin cho tất cả các đơn đặt hàng chưa hoàn thành

Cácmethodtrongrequestđược gửi bởi docker để nghe chức năng phản hồi làorders.

Các tham số được gửi bởi docker:request.Params.symbol

Các định dạng dữ liệu được mang theo bởi docker khi yêu cầu các plugin giao thức tùy chỉnh là như sau:

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "orders",
    "params" :     {"symbol" : "ETH_BTC"},     // Take the ETH_BTC trading pair for example
}

Cấu trúc của giá trị trả về cuối cùng được gửi đến docker:

Cấu trúc JSON

{
    "data": [{
        "id": 542156,
        "amount": 0.25,
        "price": 1005,
        "deal_amount": 0,
        "type": "buy",      // "buy"、"sell"
        "status": "open",   // "open"
    },{
        ...
    }]
}
    1. CancelOrder: Hủy đơn đặt hàng với ID đơn đặt hàng đã chỉ định

Cácmethodtrongrequestđược gửi bởi docker để nghe chức năng phản hồi làcancel.

Các tham số được gửi bởi docker:request.Params.id(loại chuỗi, tham số đầu tiên của hàm API được gọi bởi chiến lược),request.Params.symbol(ví dụ, btc, được gửi bởi docker dựa trên tiền tệ được đặt bởi chiến lược)

Các định dạng dữ liệu được mang theo bởi docker khi yêu cầu các plugin giao thức tùy chỉnh là như sau:

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "cancel",
    "params" :     {"symbol" : "ETH_BTC", "id" : "XXX"},     // Take an ETH_BTC trading pair with an id of "XXX" (same as the GetOrder function's parameter id) for example
}

Cấu trúc của giá trị trả về cuối cùng được gửi đến docker:

Cấu trúc JSON

{
    "data": true,        // true or false
}
    1. Gọi điện choexchange.IOchức năng của nền tảng FMZ Quant

Cácmethodtrongrequestđược gửi bởi docker để nghe chức năng phản hồi bắt đầu với_api_.

Các định dạng dữ liệu được mang theo bởi docker khi yêu cầu các plugin giao thức tùy chỉnh là như sau:

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "__api_XXX",                // XXX is the API interface for the specific exchange (base address not included)
    "params" :     {"borrow_id" : "123", "symbol" : "cny"},      // Specifically, the parameters passed into the IO function
}

Cấu trúc của giá trị trả về cuối cùng được gửi đến docker:

{
    "data": {...}       // The return value of a specific interface call
}

Ví dụ, cuộc gọi chiến lược:

var io_str = exchange.IO("api", "POST", "cancel_borrow", "symbol=cny&borrow_id=123")

Mã thử nghiệm trong plugin (tiếng đi):

fmt.Println("request.Method:", request.Method, "request.Params:", request.Params)

Dòng lệnh Plugin: 2017/08/31 10:19:59 Chạyhttp://127.0.0.1:6666/DigitalAsset

Plugin dòng lệnh in của: request.Method, request.ParamsTrong cơ thể yêu cầu được gửi bởi docker, dữ liệu được phân tích trong yêu cầu như sau:request.Method__api_cancel_borrow request.Params{"borrow_id" : "123", "symbol" : "cny"}

Bạn có thể tùy chỉnh việc xử lý nhữngexchange.IOgọi mà truy cập trực tiếp vào trao đổiAPI.

# Attention:
# When calling exchange.IO("api", "POST", "/api/v1/getAccount", "symbol=BTC_USDT"), 
# If the second parameter is not POST but: exchange.IO("api", "GET", "/api/v1/getAccount", "symbol=BTC_USDT")
# is the GET method, which is then stored in the header Http-Method in the http request accepted by the custom protocol plugin.
# So you need to refer to the following sample code for the custom protocol handling IO function implementation:
// tapiCall function definition
func (p *iStocksExchange) tapiCall(method string, params map[string]string, httpType string) (js *Json, err error) {
    ...
}

// In the OnPost function
if strings.HasPrefix(request.Method, "__api_") {
    var js *Json
    js, err = e.tapiCall(request.Method[6:], request.Params, r.Header.Get("Http-Method"))
    ...
}
  • Hỗ trợ trao đổi.GetRawJSON:

Hệ thống cơ bản tự động xử lý các cuộc gọi đếnexchange.GetRawJSON, vì vậy không cần phải thực hiện nó trong plugin.

  • Hỗ trợ trao đổi.Go:

Hệ thống cơ bản tự động xử lý các cuộc gọi đếnexchange.Go, vì vậy không cần phải xử lý nó trong plugin.

var beginTime = new Date().getTime()
var ret = exchange.Go("GetDepth")
var endTime = new Date().getTime()
Log(endTime - beginTime, "#FF0000")

// Sleep(2000)
beginTime = new Date().getTime()
Log(exchange.GetTicker())
endTime = new Date().getTime()
Log(endTime - beginTime, "#FF0000")
var depth = ret.wait()
Log("depth:", depth)

img

img

# Note: If you specify a timeout when waiting using exchange. 
#      Always make sure to obtain the final data so that the concurrent threads of the application can be reclaimed.
  • Hỗ trợ các chức năng tương lai:

Bạn cần thực hiện xử lý cụ thể trong chương trình plugin cho các chức năng tương lai. Ví dụ, đặt đòn bẩy, mã hợp đồng và hướng đặt hàng. Bạn có thể đặt một biến địa phương để ghi lại thông tin này. Để lấy vị trí, bạn sẽ cần truy cập vào API trao đổi để lấy dữ liệu thô và xử lý nó vào cấu trúc vị trí được xác định trong nền tảng FMZ, và sau đó trả lại nó.

Khi các chức năng sau được gọi trong chiến lược, định dạng củaRpcyêu cầu nhận được bởi chương trình plugin hơi khác với các giao diện khác. bạn cần phải chú ý đến định dạng của cácRpcRequestSự khác biệt chính là giá trị của params là một cấu trúc hợp chất.

SetContractType Đặt mã hợp đồng.

{"access_key":"123","method":"io","nonce":1623307269528738000,"params":{"args":["quarter"],"code":2},"secret_key":"123"}

SetDirection Đặt hướng đặt lệnh tương lai.

{"access_key":"123","method":"io","nonce":1623308734966484000,"params":{"args":["closesell"],"code":1},"secret_key":"123"}

SetMarginLevel Thiết lập đòn bẩy tương lai.

{"access_key":"123","method":"io","nonce":1623308734966939000,"params":{"args":[12],"code":0},"secret_key":"123"}

GetPosition Nhận vị trí tương lai. Khi nàoexchange.GetPosition()là:

{"access_key":"123","method":"io","nonce":1623308734967442000,"params":{"args":[],"code":3},"secret_key":"123"}

Khi nào?exchange.GetPosition("swap")là:

{"access_key":"123","method":"io","nonce":1623308734967442000,"params":{"args":["swap"],"code":3},"secret_key":"123"}
  • Complete Go Language Ví dụ về Custom Protocol Plugin (Access to Bitgo Exchange)
/*
GOOS=linux GOARCH=amd64 go build -ldflags '-s -w -extldflags -static' rest_bitgo.go
*/
package main

import (
    "bytes"
    "crypto/md5"
    "encoding/hex"
    "encoding/json"
    "errors"
    "flag"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/url"
    "sort"
    "strconv"
    "strings"
    "time"
)

func toFloat(s interface{}) float64 {
    var ret float64
    switch v := s.(type) {
    case float64:
        ret = v
    case float32:
        ret = float64(v)
    case int64:
        ret = float64(v)
    case int:
        ret = float64(v)
    case int32:
        ret = float64(v)
    case string:
        ret, _ = strconv.ParseFloat(strings.TrimSpace(v), 64)
    }
    return ret
}

func float2str(i float64) string {
    return strconv.FormatFloat(i, 'f', -1, 64)
}

func toInt64(s interface{}) int64 {
    var ret int64
    switch v := s.(type) {
    case int:
        ret = int64(v)
    case float64:
        ret = int64(v)
    case bool:
        if v {
            ret = 1
        } else {
            ret = 0
        }
    case int64:
        ret = v
    case string:
        ret, _ = strconv.ParseInt(strings.TrimSpace(v), 10, 64)
    }
    return ret
}

func toString(s interface{}) string {
    var ret string
    switch v := s.(type) {
    case string:
        ret = v
    case int64:
        ret = strconv.FormatInt(v, 10)
    case float64:
        ret = strconv.FormatFloat(v, 'f', -1, 64)
    case bool:
        ret = strconv.FormatBool(v)
    default:
        ret = fmt.Sprintf("%v", s)
    }
    return ret
}

type Json struct {
    data interface{}
}

func NewJson(body []byte) (*Json, error) {
    j := new(Json)
    err := j.UnmarshalJSON(body)
    if err != nil {
        return nil, err
    }
    return j, nil
}

func (j *Json) UnmarshalJSON(p []byte) error {
    return json.Unmarshal(p, &j.data)
}

func (j *Json) Get(key string) *Json {
    m, err := j.Map()
    if err == nil {
        if val, ok := m[key]; ok {
            return &Json{val}
        }
    }
    return &Json{nil}
}

func (j *Json) CheckGet(key string) (*Json, bool) {
    m, err := j.Map()
    if err == nil {
        if val, ok := m[key]; ok {
            return &Json{val}, true
        }
    }
    return nil, false
}

func (j *Json) Map() (map[string]interface{}, error) {
    if m, ok := (j.data).(map[string]interface{}); ok {
        return m, nil
    }
    return nil, errors.New("type assertion to map[string]interface{} failed")
}

func (j *Json) Array() ([]interface{}, error) {
    if a, ok := (j.data).([]interface{}); ok {
        return a, nil
    }
    return nil, errors.New("type assertion to []interface{} failed")
}

func (j *Json) Bool() (bool, error) {
    if s, ok := (j.data).(bool); ok {
        return s, nil
    }
    return false, errors.New("type assertion to bool failed")
}

func (j *Json) String() (string, error) {
    if s, ok := (j.data).(string); ok {
        return s, nil
    }
    return "", errors.New("type assertion to string failed")
}

func (j *Json) Bytes() ([]byte, error) {
    if s, ok := (j.data).(string); ok {
        return []byte(s), nil
    }
    return nil, errors.New("type assertion to []byte failed")
}

func (j *Json) Int() (int, error) {
    if f, ok := (j.data).(float64); ok {
        return int(f), nil
    }

    return -1, errors.New("type assertion to float64 failed")
}

func (j *Json) MustArray(args ...[]interface{}) []interface{} {
    var def []interface{}

    switch len(args) {
    case 0:
    case 1:
        def = args[0]
    default:
        log.Panicf("MustArray() received too many arguments %d", len(args))
    }

    a, err := j.Array()
    if err == nil {
        return a
    }

    return def
}

func (j *Json) MustMap(args ...map[string]interface{}) map[string]interface{} {
    var def map[string]interface{}

    switch len(args) {
    case 0:
    case 1:
        def = args[0]
    default:
        log.Panicf("MustMap() received too many arguments %d", len(args))
    }

    a, err := j.Map()
    if err == nil {
        return a
    }

    return def
}

func (j *Json) MustString(args ...string) string {
    var def string

    switch len(args) {
    case 0:
    case 1:
        def = args[0]
    default:
        log.Panicf("MustString() received too many arguments %d", len(args))
    }

    s, err := j.String()
    if err == nil {
        return s
    }

    return def
}

func (j *Json) MustInt64() int64 {
    var ret int64
    var err error
    switch v := j.data.(type) {
    case int:
        ret = int64(v)
    case int64:
        ret = v
    case float64:
        ret = int64(v)
    case string:
        if ret, err = strconv.ParseInt(v, 10, 64); err != nil {
            panic(err)
        }
    default:
        ret = 0
        //panic("type assertion to int64 failed")
    }
    return ret
}

func (j *Json) MustFloat64() float64 {
    var ret float64
    var err error
    switch v := j.data.(type) {
    case int:
        ret = float64(v)
    case int64:
        ret = float64(v)
    case float64:
        ret = v
    case string:
        v = strings.Replace(v, ",", "", -1)
        if ret, err = strconv.ParseFloat(v, 64); err != nil {
            panic(err)
        }
    default:
        ret = 0
        //panic("type assertion to float64 failed")
    }
    return ret
}

type iBitgo struct {
    accessKey     string
    secretKey     string
    currency      string
    opCurrency    string
    baseCurrency  string
    secret        string
    secretExpires int64
    apiBase       string
    step          int64
    newRate       float64
    timeout       time.Duration
    timeLocation  *time.Location
}

type MapSorter []Item

type Item struct {
    Key string
    Val string
}

func NewMapSorter(m map[string]string) MapSorter {
    ms := make(MapSorter, 0, len(m))

    for k, v := range m {
        if strings.HasPrefix(k, "!") {
            k = strings.Replace(k, "!", "", -1)
        }
        ms = append(ms, Item{k, v})
    }

    return ms
}

func (ms MapSorter) Len() int {
    return len(ms)
}

func (ms MapSorter) Less(i, j int) bool {
    //return ms[i].Val < ms[j].Val // Sort by value
    return ms[i].Key < ms[j].Key // Sort by key
}

func (ms MapSorter) Swap(i, j int) {
    ms[i], ms[j] = ms[j], ms[i]
}

func encodeParams(params map[string]string, escape bool) string {
    ms := NewMapSorter(params)
    sort.Sort(ms)

    v := url.Values{}
    for _, item := range ms {
        v.Add(item.Key, item.Val)
    }
    if escape {
        return v.Encode()
    }
    var buf bytes.Buffer
    keys := make([]string, 0, len(v))
    for k := range v {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    for _, k := range keys {
        vs := v[k]
        prefix := k + "="
        for _, v := range vs {
            if buf.Len() > 0 {
                buf.WriteByte('&')
            }
            buf.WriteString(prefix)
            buf.WriteString(v)
        }
    }
    return buf.String()
}

func newBitgo(accessKey, secretKey string) *iBitgo {
    s := new(iBitgo)
    s.accessKey = accessKey
    s.secretKey = secretKey
    s.apiBase = "https://www.bitgo.cn"
    s.timeout = 20 * time.Second
    s.timeLocation = time.FixedZone("Asia/Shanghai", 8*60*60)

    return s
}

func (p *iBitgo) apiCall(method string) (*Json, error) {
    req, err := http.NewRequest("POST", fmt.Sprintf("%s/appApi.html?%s", p.apiBase, method), nil)
    if err != nil {
        return nil, err
    }
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    return NewJson(b)
}

func (p *iBitgo) GetTicker(symbol string) (ticker interface{}, err error) {
    var js *Json
    js, err = p.apiCall("action=market&symbol=" + symbol)
    if err != nil {
        return
    }
    dic := js.Get("data")
    ticker = map[string]interface{}{
        "time": js.Get("time").MustInt64(),
        "buy":  dic.Get("buy").MustFloat64(),
        "sell": dic.Get("sell").MustFloat64(),
        "last": dic.Get("last").MustFloat64(),
        "high": dic.Get("high").MustFloat64(),
        "low":  dic.Get("low").MustFloat64(),
        "vol":  dic.Get("vol").MustFloat64(),
    }
    return
}

func (p *iBitgo) GetDepth(symbol string) (depth interface{}, err error) {
    var js *Json
    js, err = p.apiCall("action=depth&symbol=" + symbol)
    if err != nil {
        return
    }
    dic := js.Get("data")

    asks := [][2]float64{}
    bids := [][2]float64{}

    for _, pair := range dic.Get("asks").MustArray() {
        arr := pair.([]interface{})
        asks = append(asks, [2]float64{toFloat(arr[0]), toFloat(arr[1])})
    }

    for _, pair := range dic.Get("bids").MustArray() {
        arr := pair.([]interface{})
        bids = append(bids, [2]float64{toFloat(arr[0]), toFloat(arr[1])})
    }
    depth = map[string]interface{}{
        "time": js.Get("time").MustInt64(),
        "asks": asks,
        "bids": bids,
    }
    return
}

func (p *iBitgo) GetTrades(symbol string) (trades interface{}, err error) {
    var js *Json
    js, err = p.apiCall("action=trades&symbol=" + symbol)
    if err != nil {
        return
    }
    dic := js.Get("data")
    items := []map[string]interface{}{}
    for _, pair := range dic.MustArray() {
        item := map[string]interface{}{}
        arr := pair.(map[string]interface{})
        item["id"] = toInt64(arr["id"])
        item["price"] = toFloat(arr["price"])
        item["amount"] = toFloat(arr["amount"])
        // trade.Time = toInt64(arr["time"]) * 1000
        if toString(arr["en_type"]) == "bid" {
            item["type"] = "buy"
        } else {
            item["type"] = "sell"
        }
        items = append(items, item)
    }
    trades = items
    return
}

func (p *iBitgo) GetRecords(step int64, symbol string) (records interface{}, err error) {
    var js *Json
    js, err = p.apiCall(fmt.Sprintf("action=kline&symbol=%s&step=%d", symbol, step*60))
    if err != nil {
        return
    }
    items := []interface{}{}
    for _, pair := range js.Get("data").MustArray() {
        arr := pair.([]interface{})
        if len(arr) < 6 {
            err = errors.New("response format error")
            return
        }
        item := [6]interface{}{}
        item[0] = toInt64(arr[0])
        item[1] = toFloat(arr[1])
        item[2] = toFloat(arr[2])
        item[3] = toFloat(arr[3])
        item[4] = toFloat(arr[4])
        item[5] = toFloat(arr[5])

        items = append(items, item)
    }
    records = items
    return
}

func (p *iBitgo) tapiCall(method string, params map[string]string) (js *Json, err error) {
    if params == nil {
        params = map[string]string{}
    }
    params["api_key"] = p.accessKey
    h := md5.New()
    h.Write([]byte(encodeParams(params, false) + "&secret_key=" + p.secretKey))
    params["sign"] = strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
    params["action"] = method
    qs := encodeParams(params, false)

    req, err := http.NewRequest("POST", fmt.Sprintf("%s/appApi.html?%s", p.apiBase, qs), nil)
    if err != nil {
        return nil, err
    }
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    js, err = NewJson(b)
    if js != nil {
        if code := js.Get("code").MustInt64(); code != 200 {
            s := js.Get("msg").MustString()
            if s == "" {
                s = fmt.Sprintf("%v", toString(js.data))
            }
            return nil, errors.New(s)
        }
    }
    return js, err
}

func (p *iBitgo) GetAccount(symbol string) (account interface{}, err error) {
    var js *Json
    js, err = p.tapiCall("userinfo", nil)
    if err != nil {
        return
    }
    mp := js.Get("data")
    assets := map[string]map[string]interface{}{}
    for k := range mp.MustMap() {
        dic := mp.Get(k)
        if k == "free" {
            for c := range dic.MustMap() {
                if _, ok := assets[c]; !ok {
                    assets[c] = map[string]interface{}{}
                }
                assets[c]["currency"] = c
                assets[c]["free"] = dic.Get(c).MustFloat64()
            }
        } else if k == "frozen" {
            for c := range dic.MustMap() {
                if _, ok := assets[c]; !ok {
                    assets[c] = map[string]interface{}{}
                }
                assets[c]["currency"] = c
                assets[c]["frozen"] = dic.Get(c).MustFloat64()
            }
        }
    }
    accounts := []map[string]interface{}{}
    for _, pair := range assets {
        accounts = append(accounts, pair)
    }

    account = accounts
    return
}

func (p *iBitgo) Trade(side string, price, amount float64, symbol string) (orderId interface{}, err error) {
    var js *Json
    js, err = p.tapiCall("trade", map[string]string{
        "symbol": symbol,
        "type":   side,
        "price":  float2str(price),
        "amount": float2str(amount),
    })
    if err != nil {
        return
    }
    orderId = map[string]int64{"id": js.Get("orderId").MustInt64()}
    return
}

func (p *iBitgo) GetOrders(symbol string) (orders interface{}, err error) {
    var js *Json
    js, err = p.tapiCall("entrust", map[string]string{"symbol": symbol})
    if err != nil {
        return
    }
    items := []map[string]interface{}{}
    for _, ele := range js.Get("data").MustArray() {
        mp := ele.(map[string]interface{})
        item := map[string]interface{}{}
        item["id"] = toInt64(mp["id"])
        item["amount"] = toFloat(mp["count"])
        if _, ok := mp["prize"]; ok {
            item["price"] = toFloat(mp["prize"])
        } else {
            item["price"] = toFloat(mp["price"])
        }
        item["deal_amount"] = toFloat(mp["success_count"])

        if toInt64(mp["type"]) == 0 {
            item["type"] = "buy"
        } else {
            item["type"] = "sell"
        }
        item["status"] = "open"
        items = append(items, item)
    }
    return items, nil
}

func (p *iBitgo) GetOrder(orderId int64, symbol string) (order interface{}, err error) {
    var js *Json
    js, err = p.tapiCall("order", map[string]string{"id": toString(orderId)})
    if err != nil {
        return
    }

    found := false
    item := map[string]interface{}{}
    for _, ele := range js.Get("data").MustArray() {
        mp := ele.(map[string]interface{})
        if toInt64(mp["id"]) != orderId {
            continue
        }
        item["id"] = toInt64(mp["id"])
        item["amount"] = toFloat(mp["count"])
        if _, ok := mp["prize"]; ok {
            item["price"] = toFloat(mp["prize"])
        } else {
            item["price"] = toFloat(mp["price"])
        }
        item["deal_amount"] = toFloat(mp["success_count"])

        if toInt64(mp["type"]) == 0 {
            item["type"] = "buy"
        } else {
            item["type"] = "sell"
        }
        switch toInt64(mp["status"]) {
        case 1, 2:
            item["status"] = "open"
        case 3:
            item["status"] = "closed"
        case 4:
            item["status"] = "cancelled"
        }
        found = true
        break
    }
    if !found {
        return nil, errors.New("order not found")
    }
    return item, nil
}

func (p *iBitgo) CancelOrder(orderId int64, symbol string) (ret bool, err error) {
    _, err = p.tapiCall("cancel_entrust", map[string]string{"id": strconv.FormatInt(orderId, 10)})
    if err != nil {
        return
    }
    ret = true
    return
}

type RpcRequest struct {        // The fields in a struct must start with an uppercase letter, otherwise they cannot be parsed correctly. Structs can have exported and unexported fields, where fields starting with an uppercase letter are considered exported.
                                // During unmarshaling, the JSON tag of a struct is used to match and find the corresponding field. Therefore, modifiers are required in this case.
    AccessKey string            `json:"access_key"`
    SecretKey string            `json:"secret_key"`
    Nonce     int64             `json:"nonce"`
    Method    string            `json:"method"`
    Params    map[string]string `json:"params"`
}

func OnPost(w http.ResponseWriter, r *http.Request) {
    var ret interface{}
    defer func() {
        if e := recover(); e != nil {
            if ee, ok := e.(error); ok {
                e = ee.Error()
            }
            ret = map[string]string{"error": fmt.Sprintf("%v", e)}
        }
        b, _ := json.Marshal(ret)
        w.Write(b)
    }()

    b, err := ioutil.ReadAll(r.Body)
    if err != nil {
        panic(err)
    }
    var request RpcRequest
    err = json.Unmarshal(b, &request)
    if err != nil {
        panic(err)
    }
    e := newBitgo(request.AccessKey, request.SecretKey)
    symbol := request.Params["symbol"]
    if s := request.Params["access_key"]; len(s) > 0 {
        e.accessKey = s
    }
    if s := request.Params["secret_key"]; len(s) > 0 {
        e.secretKey = s
    }
    if symbolIdx, ok := map[string]int{
        "btc":  1,
        "ltc":  2,
        "etp":  3,
        "eth":  4,
        "etc":  5,
        "doge": 6,
        "bec":  7,
    }[strings.Replace(strings.ToLower(symbol), "_cny", "", -1)]; ok {
        symbol = toString(symbolIdx)
    }
    var data interface{}
    switch request.Method {
    case "ticker":
        data, err = e.GetTicker(symbol)
    case "depth":
        data, err = e.GetDepth(symbol)
    case "trades":
        data, err = e.GetTrades(symbol)
    case "records":
        data, err = e.GetRecords(toInt64(request.Params["period"]), symbol)
    case "accounts":
        data, err = e.GetAccount(symbol)
    case "trade":
        side := request.Params["type"]
        if side == "buy" {
            side = "0"
        } else {
            side = "1"
        }
        price := toFloat(request.Params["price"])
        amount := toFloat(request.Params["amount"])
        data, err = e.Trade(side, price, amount, symbol)
    case "orders":
        data, err = e.GetOrders(symbol)
    case "order":
        data, err = e.GetOrder(toInt64(request.Params["id"]), symbol)
    case "cancel":
        data, err = e.CancelOrder(toInt64(request.Params["id"]), symbol)
    default:
        if strings.HasPrefix(request.Method, "__api_") {
            data, err = e.tapiCall(request.Method[6:], request.Params)
        } else {
            panic(errors.New(request.Method + " not support"))
        }
    }
    if err != nil {
        panic(err)
    }
    ret = map[string]interface{}{
        "data": data,
    }

    return
}

func main() {
    var addr = flag.String("b", "127.0.0.1:6666", "bind addr")
    flag.Parse()
    if *addr == "" {
        flag.Usage()
        return
    }
    basePath := "/exchange"
    log.Println("Running ", fmt.Sprintf("http://%s%s", *addr, basePath), "...")
    http.HandleFunc(basePath, OnPost)
    http.ListenAndServe(*addr, nil)
}

Thêm nữa