avatar of 发明者量化-小小梦 发明者量化-小小梦
tập trung vào tin nhắn riêng tư
4
tập trung vào
1271
Người theo dõi

Giao thức chung của nền tảng giao dịch định lượng của nhà phát minh được kết nối với sàn giao dịch tùy chỉnh

Được tạo ra trong: 2017-08-24 16:29:56, cập nhật trên: 2021-06-10 15:33:02
comments   23
hits   17429

Tài liệu sử dụng GPO

Có thể truy cập vào bất kỳ một trong những cung cấp bằng cách sử dụng giao thức chungAPIgiao dịch trên các sàn giao dịch, không giới hạn các giao thức API cụ thể, hoặcrestwebsocketfix…có thể truy cập được. Ví dụ về giao thức Python chung: https://www.fmz.com/strategy/101399

  • ### 1. Chạy các plugin, thiết lập cổng

Đặt địa chỉ và cài đặt cổng của plugin GNU General Public License. Ví dụ:http://127.0.0.1:6666/DigitalAssetHoặc:http://127.0.0.1:6666/exchange

Tại sao chúng ta lại thiết lập những thứ này?IPCon đườngVải len? Vì ởNhà phát minh định lượngTrangTrung tâm điều khiểnBước nhảyThêm sàn giao dịchTrang, chọn mảng GPO, hiển thị ngoàiAPI-KEY, có một ô dịch vụ địa chỉ ô, địa chỉ dịch vụ là nói cho người quản lý đến khi nào truy cập vào IP và cổng ((người quản lý và các chương trình bổ sung giao thức chung có thể không chạy trên cùng một thiết bị)). Ví dụ đầy đủ địa chỉ dịch vụhttp://127.0.0.1:6666/DigitalAssetDigitalAssetĐây là những cái tên tự định nghĩa, và chỉ là ví dụ.

Trang giao dịch của nhà phát minh có nội dung như sau: Thông thường, thông tin tài khoản giao dịch chỉ cần được cấu hìnhaccess keysecret keyTuy nhiên, một số giao diện API của sàn giao dịch yêu cầu truyền mật khẩu giao dịch (ví dụ như giao diện đặt hàng của một số sàn giao dịch), khi đó vì trang giao thức chung không có điều khiển dư thừa để viết thông tin này, khi gặp API của các sàn giao dịch như vậy, chúng ta có thể viết thông tin cấu hình dư thừa cần được truyền vàosecret keyNếu thông tin không nhạy cảm, bạn có thể viết:access key), và sau đó làm chuỗi trong chương trình plugin GPOsplitHoạt động, tách các dữ liệu này ra, như trong ví dụ trên.

Giao thức chung của nền tảng giao dịch định lượng của nhà phát minh được kết nối với sàn giao dịch tùy chỉnh

Sau đó, chúng tôi xử lý trong plugin, và chúng tôi cóXXX_PassWord。 Ví dụ, trong ví dụ hoàn chỉnh cuối cùng của bài viết này, hàm newBitgo:

  func newBitgo(accessKey, secretKey string) *iBitgo {
      s := new(iBitgo)
      s.accessKey = accessKey
      s.secretKey = secretKey
      // 在此可以分离secretKey中的额外配置信息,可以写成如下注释中的内容
      /*
      arr := strings.SplitN(secretKey, ",", 2)
      if len(arr) != 2 {
          panic("配置错误!")
      }
      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
  }

Giao thức chung của nền tảng giao dịch định lượng của nhà phát minh được kết nối với sàn giao dịch tùy chỉnh

Các plugin toàn diện của GPLmainVí dụ về hàm: GoTác giả:

  func main(){
      var addr = flag.String("b", "127.0.0.1:6666", "bing addr")   // 设置命令行参数,默认值描述,端口设置6666
      flag.Parse()                                                 // 解析命令行
      if *addr == "" {
          flag.Usage()                                             // 显示命令行描述
          return 
      }
      basePath := "/DigitalAsset"
      log.Println("Running ", fmt.Sprintf("http://%s%s", *addr, basePath), "...")   // 打印监听端口信息
      http.HandleFunc(basePath, OnPost)
      http.ListenAndServe(*addr, nil)
  }
  • #### 2. Chức năng đáp ứng

GNU plugin chương trình liên tục theo dõi các cổng được chỉ định, có yêu cầu gửi đếnrequestMột khi có một yêu cầu, hàm đáp ứng sẽ được gọi để thực hiện đáp ứng, sau đó phân tích các tham số trong dữ liệu yêu cầu, dữ liệu yêu cầu được gửi bởi người quản lý là:

  /* request的JSON结构,发明者量化调用GetTicker,托管者发送给通用协议插件情况举例(各个API调用时,params的值可能不一样,此处method为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",
                     ...
                 },                       // 各个请求参数略有区别。即在策略中调用不同的 发明者量化 API会有不同的参数, 在下文各个API 有介绍说明。
  }
  */

Vì vậy, dựa trên yêu cầu nhận được trong chương trình plugin GPO, cấu trúc thu được sau khi dữ liệu cơ thể JSON được giải trình tự ngượcrequestTRONGMethodChúng ta có thể sử dụngswitchĐể phân loại xử lý các nhà phát minh khác nhau về định lượng API, là để xác định các chính sách chạy trên máy chủ được gọi là người phát minh định lượngAPI(Đọc:

GoVí dụ về ngôn ngữ:

  switch request.Method {    // 此处request.Method的M为大写,通用协议程序接收到的请求主体中为JSON数据,在Go语言中反JSON序列化(Unmarshal)为结构体,字段首字母必须大写
    case "accounts" :        // 当托管者上的机器人策略中调用了exchange.GetAccount()函数,托管者会发送来请求,其中Body携带的数据中method属性值为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:
        ...

Các chi nhánh này thực hiện dữ liệu trả về được viết vào cấu trúc mà chương trình bổ sung GPL sẽ đáp ứng, đáp ứng yêu cầu của người quản lý.

Ví dụ về ngôn ngữ Go:

  defer func(){                                // 处理收尾工作 
        if e := recover(); e != nil {          // recover()函数用于捕获panic,e != nil即有错误发生
            if ee, ok := e.(error); ok {       // 类型推导,推导成功把ee.Error()赋值给e
                e = ee.Error()                 // 调用Error方法获取返回的错误字符串
            }
            ret = map[string]string{"error": fmt.Sprintf("%v", e)}
        }
        b, _ := json.Marshal(ret)              // 把本次调用获取的结果ret编码,赋值给b,写入响应指针
        w.Write(b)
        //fmt.Println("after w.Write the b is", string(b))     // 测试
    }()
  • #### 3. Loại gọi API

Có hai loại: Các giao diện công cộng không cần chữ ký như:

GetTicker()

GetDepth()

GetTrades()

GetRecords(period)

  1. Giao diện người dùng cần chữ ký, chẳng hạn như:

BuySell

GetOrder(id)

GetOrders()

GetAccount()

CancelOrder(id) … Các giao dịch khác nhau có thể ký theo cách khác nhau và cần phải viết cụ thể theo nhu cầu.

  • ### 4, Khi các nhà phát minh định lượng các cuộc gọi APIPlugin GPOChủ nhàĐịnh dạng dữ liệu tương tác:

Một số nhà phát minh đã định lượng các API như:GetName()GetLabel()Chức năng tương tự, khi được gọi sẽ không hướng đếnPlugin GPOGửi yêu cầu exchange.GetName()Các giao dịch được cấu hình theo plugin chung sẽ trả về “Exchange” khi được gọi.

  • 1、GetTicker: Để có được dữ liệu hiện tại.

    Chủ nhàGửi đến chức năng phản hồi ngherequestTRONGmethodĐối với:ticker

    Trình quản trị gửi tham số:request.Params.symbol: Được gửi bởi người quản lý theo tùy chọn của trang bot.

    Định dạng dữ liệu được yêu cầu chủ thể mang theo khi người quản lý yêu cầu plugin GPO (JSON)

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "ticker",
        "params" :     {"symbol" : "ETH_BTC"},     // 以ETH_BTC交易对举例
    }
    

    Cấu trúc giá trị trả về cuối cùng được gửi đến người quản lý: ((đó là định dạng dữ liệu được trả về cho người quản lý sau khi plugin GPO yêu cầu giao diện giao dịch)

    Cấu trúc JSON

    {
        "data": {
            "time": 1500793319499,  // 毫秒时间戳,整型
            "buy": 1000,            // 以下浮点型
            "sell": 1001,
            "last": 1005,
            "high": 1100,
            "low": 980,
            "vol": 523,
        }
    }
    
  • 2、GetRecords:Dữ liệu K-line được cung cấp bởi sàn giao dịch.

    Trình quản lý gửi đến chức năng phản hồi ngherequestTRONGmethodĐối với:records

    Trình quản trị gửi tham số:request.Params.periodCác giá trị liên quan:exchange.GetRecordsCác tham số đầu tiên của hàm, thực tếrequest.Params.periodChu kỳ được biểu thị bằng số phút, ví dụ như chu kỳ ngày là60*24Ngay lập tức1440request.Params.symbol: Người quản lý gửi theo định dạng của đồng tiền.

    Định dạng dữ liệu mà người quản lý yêu cầu chủ thể mang theo khi yêu cầu plugin GPL

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "records",
        "params" :     {"symbol" : "ETH_BTC", "period" : "1440"},     // 以ETH_BTC交易对,K线周期为日线举例
    }
    

    Cấu trúc giá trị trả về cuối cùng được gửi đến người quản lý:

    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 thử nghiệm ngôn ngữ Go:

    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}
    }
    

    Nền tảng định lượng của nhà phát minhLogtrình diễnrecordsDữ 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, nguyên tố đầu tiên trong mảng 2 chiều làintLoại, đại diện cho thời gian 。 2. Người quản lý sẽ tự động cho thời gian 乘以1000, trên có thể thấy 。

  • 3、GetDepth:Để có được thông tin sâu về các sàn giao dịch, hãy đặt hàng thô lỗ, bán một, bán hai… mua một, mua hai…

    Trình quản lý gửi đến chức năng phản hồi ngherequestTRONGmethodĐối với:depth

    Trình quản trị gửi tham số:request.Params.symbol: Tiền được gửi bởi người quản lý theo chính sách của họ

    Định dạng dữ liệu mà người quản lý yêu cầu chủ thể mang theo khi yêu cầu plugin GPL

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "depth",
        "params" :     {"symbol" : "ETH_BTC"},     // 以ETH_BTC交易对举例
    }
    

    Cấu trúc giá trị trả về cuối cùng được gửi đến người quản lý:

    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], ... ],
        }
    }
    
  • 4、GetTrades:Nhận được hồ sơ giao dịch của toàn bộ sàn giao dịch trong một khoảng thời gian do sàn giao dịch cung cấp (không phải là hồ sơ giao dịch của riêng bạn)

    Trình quản lý gửi đến chức năng phản hồi ngherequestTRONGmethodĐối với:trades

    Trình quản trị gửi tham số:request.Params.symbolVí dụ:btcCác đồng tiền được gửi bởi người quản lý theo chiến lược được thiết lập.

    Định dạng dữ liệu mà người quản lý yêu cầu chủ thể mang theo khi yêu cầu plugin GPL

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "trades",
        "params" :     {"symbol" : "ETH_BTC"},     // 以ETH_BTC交易对举例
    }
    

    Cấu trúc giá trị trả về cuối cùng được gửi đến người quản lý:

    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",
            },{
                ...
            }
        ]
    }
    
  • 5、GetAccount:Nhận thông tin tài sản tài khoản

    Trình quản lý gửi đến chức năng phản hồi ngherequestTRONGmethodĐối với:accounts

    Trustee send parameter: ((Lưu ý! thường là lấy tất cả tài sản của tài khoản!, tùy thuộc vào giao diện giao dịch, là lấy riêng hoặc lấy thông tin tổng tài sản)

    Định dạng dữ liệu mà người quản lý yêu cầu chủ thể mang theo khi yêu cầu plugin GPL

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "accounts",
        "params" :     {},                         
    }
    

    Cấu trúc giá trị trả về cuối cùng được gửi đến người quản lý:

    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" : {...}             // 可以写入插件访问交易所时,交易所返回的原始信息(response)
    }
    
  • 6、Buy、Sell:Gửi đơn đặt hàng, đặt hàng giao dịch.

    Trình quản lý gửi đến chức năng phản hồi ngherequestTRONGmethodĐối với:trade

    Trình quản trị gửi tham số:request.Params.typeNgười quản lý theo yêu cầu:exchange.BuyHoặc làexchange.SellGửi,request.Params.price“Được gọi trong chiến lược:APICác tham số đầu tiên của hàmrequest.Params.amount“Được gọi trong chiến lược:APICác tham số thứ hai của hàmrequest.Params.symbol: Người quản lý gửi theo định dạng của đồng tiền.

    Định dạng dữ liệu mà người quản lý yêu cầu chủ thể mang theo khi yêu cầu plugin GPL

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "trade",
        "params" :     {
                           "symbol" : "ETH_BTC", 
                           "type" : "buy", 
                           "price" : "1000", 
                           "amount" : "1"
                       },                          // 以ETH_BTC交易对,"type":"buy"买请求,价格1000,数量1举例
    }
    

    Cấu trúc giá trị trả về cuối cùng được gửi đến người quản lý:

    Cấu trúc JSON

    {
        "data": {
            "id": 125456,      // 下单后返回的订单id
                               // 如果订单id是"asdf346sfasf532"这样的字符串形式
                               // 此处id也可以是字符串类型
        }
    }
    
  • 7、GetOrder:Nhận thông tin đơn đặt hàng theo số đặt hàng đã chỉ định

    Trình quản lý gửi đến chức năng phản hồi ngherequestTRONGmethodĐối với:order

    Trình quản trị gửi tham số:request.Params.idrequest.Params.symbol

    Định dạng dữ liệu mà người quản lý yêu cầu chủ thể mang theo khi yêu cầu plugin GPL

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "order",
        "params" :     {"symbol" : "ETH_BTC", "id" : "XXX"},     // 以ETH_BTC交易对,订单id为XXX举例,注意有些交易所的订单号是数字形式的订单ID,如123456,有些交易所的订单号是字符串形式的ID,如poimd55sdfheqxv,具体看交易所的订单ID格式
    }
    

    Cấu trúc giá trị trả về cuối cùng được gửi đến người quản lý:

    Cấu trúc JSON

    { 
        "data": {
            "id": 2565244,
            "amount": 0.15,
            "price": 1002,
            "status": "open",    // "open":挂起状态、"closed":完成关闭状态、"cancelled":已取消
            "deal_amount": 0,
            "type": "buy",       // "buy"、"sell"
            "avg_price": 0,      // 如果交易所没有提供,在处理时可以赋值为0
        }
    }
    
  • 8、GetOrders: Nhận thông tin về tất cả các đơn hàng chưa hoàn thành

    Trình quản lý gửi đến chức năng phản hồi ngherequestPhương thức của nó là:orders

    Trình quản trị gửi tham số:request.Params.symbol

    Định dạng dữ liệu mà người quản lý yêu cầu chủ thể mang theo khi yêu cầu plugin GPL

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "orders",
        "params" :     {"symbol" : "ETH_BTC"},     // 以ETH_BTC交易对举例
    }
    

    Cấu trúc giá trị trả về cuối cùng được gửi đến người quản lý:

    Cấu trúc JSON

    {
        "data": [{
            "id": 542156,
            "amount": 0.25,
            "price": 1005,
            "deal_amount": 0,
            "type": "buy",      // "buy"、"sell"
            "status": "open",   // "open"
        },{
            ...
        }]
    }
    
  • 9、CancelOrder: Hủy bỏ ủy thác đơn đặt hàng với số đặt hàng được chỉ định

    Trình quản lý gửi đến chức năng phản hồi ngherequestTRONGmethodĐối với:cancel

    Trình quản trị gửi tham số:request.Params.id: kiểu chuỗi, tham số đầu tiên của hàm API mà chính sách gọi,request.Params.symbol:btc (ví dụ) được gửi bởi người quản lý theo chính sách của họ

    Định dạng dữ liệu mà người quản lý yêu cầu chủ thể mang theo khi yêu cầu plugin GPL

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "cancel",
        "params" :     {"symbol" : "ETH_BTC", "id" : "XXX"},     // 以ETH_BTC交易对,id为"XXX"(同GetOrder函数的参数id一样),举例
    }
    

    Cấu trúc giá trị trả về cuối cùng được gửi đến người quản lý:

    Cấu trúc JSON

    {
        "data": true,        // true or false
    }
    
  • 10、IO: Chức năng exchange.IO gọi nền tảng định lượng của nhà phát minh

    Trình quản lý gửi đến chức năng phản hồi ngherequestTRONGmethodĐể__api_Tên của phương thức bắt đầu từ:

    Định dạng dữ liệu mà người quản lý yêu cầu chủ thể mang theo khi yêu cầu plugin GPL

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "__api_XXX",                // XXX为具体交易所的API接口(不包含基地址)
        "params" :     {"borrow_id" : "123", "symbol" : "cny"},      // 具体是传入IO函数的参数
    }
    

    Cấu trúc giá trị trả về cuối cùng được gửi đến người quản lý:

    {
        "data": {...}       // 具体的接口调用的返回值
    }
    

    Ví dụ, một 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 (go):

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

    Cài đặt từ dòng lệnh: 2017/08/31 10:19:59 Running http://127.0.0.1:6666/DigitalAsset

    Request.Method,request.Params được in trong dòng lệnh của plugin Trong các yêu cầu được phân tích dữ liệu trong Body của người quản lý:request.MethodĐối với:__api_cancel_borrow Trong các yêu cầu được phân tích dữ liệu trong Body của người quản lý:request.ParamsĐối với:{"borrow_id" : "123", "symbol" : "cny"}

    Có thể tùy chỉnh xử lý các giao dịch truy cập trực tiếpAPIcủaexchange.IOGọi.

    # 注意:
    # 在调用exchange.IO("api", "POST", "/api/v1/getAccount", "symbol=BTC_USDT")时,
    # 如果第二个参数不是POST,而是:exchange.IO("api", "GET", "/api/v1/getAccount", "symbol=BTC_USDT")
    # 是GET方法,这时在通用协议插件接受到的http请求中头部Http-Method中储存的才是GET,
    # 所以在通用协议处理IO函数实现时,需要参考以下范例代码:
    // tapiCall函数定义
    func (p *iStocksExchange) tapiCall(method string, params map[string]string, httpType string) (js *Json, err error) {
        ...
    }
    
    
    // 在OnPost函数中
    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ợ cho exchange.GetRawJSON

    Tự động hóa tầng dướiexchange.GetRawJSONKhông cần phải thực hiện trong plugin.

  • Hỗ trợ cho Exchange.Go

    Tự động hóa tầng dướiexchange.GoKhông cần phải xử lý 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)
    

    Giao thức chung của nền tảng giao dịch định lượng của nhà phát minh được kết nối với sàn giao dịch tùy chỉnh

    Giao thức chung của nền tảng giao dịch định lượng của nhà phát minh được kết nối với sàn giao dịch tùy chỉnh

    # 注意:使用exchange.Go在wait的时候如果指定了超时时间, 
    #      一定要确保获取到最终的数据,这样申请的并发线程才能回收。
    
  • Hỗ trợ cho hàm tương lai

    Cần thực hiện xử lý cụ thể trong chương trình plugin, chẳng hạn như thiết lập đòn bẩy, mã hợp đồng, hướng đặt hàng, có thể thiết lập một bản ghi biến cục bộ, để có được vị trí cần truy cập giao diện giao dịch để lấy dữ liệu gốc và xử lý cấu trúc vị trí được định nghĩa trên nền tảng FMZ và trả về. Các chức năng sau đây được nhận khi chính sách gọiRpcĐịnh dạng yêu cầu có một chút khác biệt so với các giao diện khác, cần lưu ý rằng các chương trình bổ sung GPLRpcRequestĐịnh dạng của params, khác biệt chính là giá trị của params là một cấu trúc phức hợp.

    • SetContractType
      Thiết lập mã hợp đồng
      {"access_key":"123","method":"io","nonce":1623307269528738000,"params":{"args":["quarter"],"code":2},"secret_key":"123"}
    
    • SetDirection Thiết lập tương lai theo một hướng.
      {"access_key":"123","method":"io","nonce":1623308734966484000,"params":{"args":["closesell"],"code":1},"secret_key":"123"}
    
    • SetMarginLevel Đặt đòn bẩy tương lai.
      {"access_key":"123","method":"io","nonce":1623308734966939000,"params":{"args":[12],"code":0},"secret_key":"123"}
    
    • GetPosition Nhận được quyền nắm giữ hợp đồng tương lai. Khiexchange.GetPosition()Khi gọi điện:
      {"access_key":"123","method":"io","nonce":1623308734967442000,"params":{"args":[],"code":3},"secret_key":"123"}
    

    Khiexchange.GetPosition("swap")Khi gọi điện:

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

  • ### Ví dụ về ngôn ngữ Go với các plugin GPO hoàn chỉnh.
/*
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 // 按值排序
    return ms[i].Key < ms[j].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 {        // 结构体里的字段首字母必须大写,否则无法正常解析,结构体有导出和未导出,大写字母开头为导出。
                                // 在Unmarshal的时候会  根据 json 匹配查找该结构体的tag, 所以此处需要修饰符
    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)
}