Platform Perdagangan Kuantum FMZ Protokol Sesuai Akses kepada Pertukaran Sesuai

Penulis:Lydia, Dicipta: 2023-07-12 15:29:54, Dikemas kini: 2024-01-03 21:01:07

img

Platform Perdagangan Kuantum FMZ Protokol Sesuai Akses kepada Pertukaran Sesuai

Dokumen Penggunaan Protokol Sesuai

Anda boleh menggunakan protokol umum ini untuk mengakses mana-mana pertukaran yang menyediakan perdagangan API, protokol API tertentu tidak terhad, sama ada ia berehat, websocket, memperbaiki... Semua boleh diakses untuk digunakan. Contoh protokol adat Python:https://www.fmz.com/strategy/101399

1. operasi protokol tersuai plug-in, tetapan pelabuhan

Tetapan untuk alamat mendengar dan port Custom Protocol Plugin boleh seperti berikut: Contohnya:http://127.0.0.1:6666/DigitalAssetatauhttp://127.0.0.1:6666/exchange.

Mengapa kita perlu menetapkan iniIPdanlaluan- Tidak. Ini kerana apabilamenambah pertukaranhalaman dalamFMZ Quant Platform Dashboard, dengan memilih pilihan Protocol Custom akan memaparkan Alamat Perkhidmatan sebagai tambahan kepadaAPI-KEY. Alamat perkhidmatan ini memberitahu docker di mana untuk mengakses IP dan port (docker dan Custom Protocol plugin program mungkin tidak berjalan pada peranti yang sama).http://127.0.0.1:6666/DigitalAsset. DigitalAssetadalah hanya contoh dan boleh diganti dengan nama pilihan anda.

Pada halaman FMZ Quant Platforms Add Exchange, biasanya konfigurasi pertukaran hanya memerlukanaccess keydansecret key. Walau bagaimanapun, beberapa pertukaran API antara muka memerlukan kata laluan dagangan yang akan dihantar (contohnya, antara muka penempatan pesanan pertukaran tertentu). Dalam kes sedemikian, kerana halaman Protokol Sesuai tidak mempunyai kawalan tambahan untuk memasukkan maklumat ini, kita boleh memasukkan maklumat konfigurasi tambahan yang diperlukan dalamsecret key(atauaccess keyjika maklumat tidak sensitif). Kemudian, dalam program Custom Protocol plugin, kita boleh menjalankan rentetansplitoperasi untuk memisahkan data ini, seperti yang ditunjukkan dalam gambar contoh.

img

Dan kemudian dalam pemalam, memprosesnya untuk mendapatkanXXX_PassWord. Sebagai contoh, dalam contoh lengkap di akhir catatan ini, dalam fungsi 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
}

Menganalisis parameter dalam data permintaan sekali lagi, docker menghantar data permintaan sebagai:

"secret_key" : "XXX",

Plugin menerima data masuk yang mengandungi jenis maklumat ini dan memisahkan XXX_PassWord daripadanya berdasarkan pemisah koma, supaya ia mendapat data tambahan yang dilewatkan.

Contoh plugin protokol tersuai secara keseluruhanmainfungsi:GoPenerangan bahasa:

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. Fungsi tindak balas

Program Custom Protocol Plugin terus mendengar pada port yang ditentukan untuk masukrequestApabila permintaan diterima, ia memanggil fungsi tindak balas untuk melaksanakan dan kemudian menganalisis parameter dari data permintaan. Data permintaan yang dihantar oleh docker adalah:

/* 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.
}
*/

Jadi, berdasarkanMethodbidang dalamrequeststruktur yang diperoleh oleh JSON deserializing permintaan Data badan yang diterima dalam program Universal Protocol Plugin, kita boleh menggunakanswitchpernyataan untuk mengkategorikan dan mengendalikan pelbagai FMZ Quant API yang dipanggil pada docker (iaitu, mengenal pasti yang FMZ QuantAPIstrategi yang dijalankan pada doker memanggil):

Contoh dalamGoBahasa:

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:
      ...

Selepas menjalankan cawangan ini, data yang dikembalikan harus ditulis ke dalam struktur yang akan digunakan oleh program Custom Protocol Plugin untuk menjawab permintaan dockers.

Contoh dalam bahasa 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. Jenis panggilan API

Secara kasar dibahagikan kepada dua kategori:

  1. antara muka awam yang tidak memerlukan tandatangan, contohnya:

GetTicker()

GetDepth()

GetTrades()

GetRecords(period)

  1. antara muka pengguna yang perlu ditandatangani, seperti:

Buy, Sell

GetOrder(id)

GetOrders()

GetAccount()

CancelOrder(id)... yang penting. Kaedah tandatangan boleh berbeza dari pertukaran ke pertukaran, dan perlu ditulis secara khusus mengikut keperluan.

4. Format data untuk interaksi antara Custom Protocol Plugin dan docker apabila memanggil pelbagai antara muka FMZ Quant API:

Sesetengah antara muka FMZ Quant API, sepertiGetName(), GetLabel(), dll., jangan menghantar permintaan kepadaPlugin Protokol tersuaiapabila dipanggil. Apabila memanggilexchange.GetName(), pertukaran yang dikonfigurasikan dalam pemalam sejagat akan mengembalikan Exchange.

    1. GetTicker: Digunakan untuk mendapatkan data ticker semasa.

Peraturanmethoddalamrequestdihantar olehpelabuhankepada fungsi tindak balas mendengar adalahticker.

Docker menghantar parameter:request.Params.symbol, yang dihantar oleh docker berdasarkan mata wang yang ditetapkan pada halaman robot.

Format data (JSON) yang dibawa dalam badan permintaan apabila docker meminta 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
}

Struktur nilai pulangan akhir yang dihantar ke docker: (iaitu format di mana data dikembalikan ke docker selepas antara muka pertukaran diminta oleh pemalam protokol biasa)

Struktur 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: Digunakan untuk mendapatkan data K-line yang disediakan oleh pertukaran. (Berdasarkan parameter yang diminta oleh docker)

Peraturanmethoddalamrequestdihantar oleh pelabuhan ke fungsi tindak balas mendengar adalahrecords.

Docker menghantar parameter:request.Params.period, yang dikaitkan dengan parameter pertamaexchange.GetRecordsFungsi sebenarrequest.Params.periodmewakili tempoh dalam minit. contohnya tempoh harian adalah60*24, yang merupakan1440. request.Params.symboldihantar oleh pelabuhan berdasarkan mata wang yang ditetapkan.

Format data yang dibawa dalam badan permintaan apabila docker meminta 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
}

Struktur nilai pulangan akhir yang dihantar ke pelabuhan:

Struktur 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],
            ...
    ]
}

Data ujian bahasa 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}
}

Platform kuant FMZLogPemandanganrecordsdata:

[
    {"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}
]

Nota: 1. elemen pertama dalam array dua dimensi adalah jenisintdan mewakili cap waktu. 2. Docker akan secara automatik mengalikan cap masa dengan 1000, seperti yang dinyatakan di atas.

    1. GetDepth: Memulihkan maklumat kedalaman (buku pesanan, ask1, ask2... tawaran1, tawaran2...) dari pertukaran.

Peraturanmethoddalamrequestdihantar oleh pelabuhan ke fungsi tindak balas mendengar adalahdepth.

Docker menghantar parameter:request.Params.symbol, yang dihantar oleh pelabuhan berdasarkan mata wang yang ditetapkan dalam strategi.

Format data yang dibawa dalam badan permintaan apabila docker meminta 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
}

Struktur nilai pulangan akhir yang dihantar ke pelabuhan:

Struktur 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: Dapatkan rekod dagangan keseluruhan bursa dalam tempoh masa tertentu (tidak termasuk dagangan sendiri)

Peraturanmethoddalamrequestdihantar oleh pelabuhan ke fungsi tindak balas mendengar adalah:trades.

Parameter yang dihantar oleh pelabuhan: Nilairequest.Params.symboladalah mata wang dagangan, contohnya:btc, yang dihantar oleh docker berdasarkan tetapan strategi.

Format data yang dibawa oleh docker apabila meminta plugin protokol tersuai adalah seperti berikut

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

Struktur nilai pulangan akhir yang dihantar ke pelabuhan:

Struktur 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. Dapatkan maklumat aset akaun.

Peraturanmethoddalamrequestdihantar oleh pelabuhan ke fungsi tindak balas mendengar adalah:accounts.

Parameter yang dihantar oleh docker: (Catatan: Secara amnya, ia adalah untuk mendapatkan semua aset akaun! Sila rujuk antara muka pertukaran untuk melihat jika ia adalah untuk mendapatkan aset individu atau maklumat aset keseluruhan)

Format data yang dibawa oleh docker apabila meminta plugin protokol tersuai adalah seperti berikut

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

Struktur nilai pulangan akhir yang dihantar ke pelabuhan:

Struktur 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. Beli, Jual: Letakkan pesanan untuk perdagangan (urutan pasaran atau perintah had).

Peraturanmethoddalamrequestdihantar oleh pelabuhan ke fungsi tindak balas mendengar adalah:trade.

Parameter yang dihantar oleh pelabuhan:request.Params.type: dihantar oleh docker berdasarkan sama ada ia memanggilexchange.Buyatauexchange.Sell, request.Params.price: parameter pertamaAPIfungsi dipanggil dalam strategi,request.Params.amount: parameter keduaAPIfungsi dipanggil dalam strategi,request.Params.symbol: dihantar oleh pelabuhan berdasarkan mata wang dagangan yang ditetapkan.

Format data yang dibawa oleh docker apabila meminta plugin protokol tersuai adalah seperti berikut

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

Struktur nilai pulangan akhir yang dihantar ke pelabuhan:

Struktur 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: Dapatkan maklumat pesanan tertentu mengikut ID pesanan

Peraturanmethoddalamrequestdihantar oleh pelabuhan ke fungsi tindak balas mendengar adalah:order.

Parameter yang dihantar oleh pelabuhan:request.Params.id, request.Params.symbol.

Format data yang dibawa oleh docker apabila meminta plugin protokol tersuai adalah seperti berikut

{
    "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.
}

Struktur nilai pulangan akhir yang dihantar ke pelabuhan:

Struktur 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: Dapatkan maklumat untuk semua pesanan yang belum diisi

Peraturanmethoddalamrequestdihantar oleh pelabuhan ke fungsi tindak balas mendengar adalahorders.

Parameter yang dihantar oleh pelabuhan:request.Params.symbol

Format data yang dibawa oleh docker apabila meminta plugin protokol tersuai adalah seperti berikut

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

Struktur nilai pulangan akhir yang dihantar ke pelabuhan:

Struktur JSON

{
    "data": [{
        "id": 542156,
        "amount": 0.25,
        "price": 1005,
        "deal_amount": 0,
        "type": "buy",      // "buy"、"sell"
        "status": "open",   // "open"
    },{
        ...
    }]
}
    1. BatalkanOrder: Batalkan pesanan dengan ID pesanan yang ditentukan

Peraturanmethoddalamrequestdihantar oleh pelabuhan ke fungsi tindak balas mendengar adalahcancel.

Parameter yang dihantar oleh pelabuhan:request.Params.id(jenis rentetan, parameter pertama fungsi API yang dipanggil oleh strategi),request.Params.symbol(contohnya, btc, dihantar oleh pelabuhan berdasarkan mata wang yang ditetapkan oleh strategi)

Format data yang dibawa oleh docker apabila meminta plugin protokol tersuai adalah seperti berikut

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

Struktur nilai pulangan akhir yang dihantar ke pelabuhan:

Struktur JSON

{
    "data": true,        // true or false
}

Peraturanmethoddalamrequestdihantar oleh pelabuhan ke fungsi tindak balas mendengar bermula dengan_api_.

Format data yang dibawa oleh docker apabila meminta plugin protokol tersuai adalah seperti berikut

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

Struktur nilai pulangan akhir yang dihantar ke pelabuhan:

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

Sebagai contoh, panggilan strategi:

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

Kod ujian dalam pemalam (go bahasa):

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

Baris perintah pemalam: 2017/08/31 10:19:59 Berlarihttp://127.0.0.1:6666/DigitalAsset

Pencetakan baris arahan plugin: request.Method, request.ParamsDalam badan permintaan yang dihantar oleh docker, data yang dianalisis dalam permintaan adalah seperti berikut:request.Methodadalah__api_cancel_borrow request.Paramsadalah{"borrow_id" : "123", "symbol" : "cny"}

Anda boleh menyesuaikan penanganan iniexchange.IOpanggilan yang terus mengakses pertukaranAPI.

# 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"))
    ...
}
  • Sokongan untuk pertukaran.GetRawJSON:

Sistem asas secara automatik mengendalikan panggilan keexchange.GetRawJSON, jadi tidak perlu untuk melaksanakan ia dalam pemalam.

  • Sokongan untuk pertukaran.Pergi:

Sistem asas secara automatik mengendalikan panggilan keexchange.Go, jadi tidak perlu mengendalikannya dalam pemalam.

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.
  • Sokongan untuk fungsi niaga hadapan:

Anda perlu melaksanakan pengendalian khusus dalam program pemalam untuk fungsi niaga hadapan. Sebagai contoh, menetapkan leverage, kod kontrak, dan arah pesanan. Anda boleh menetapkan pembolehubah tempatan untuk merakam maklumat ini. Untuk mendapatkan kedudukan, anda perlu mengakses API pertukaran untuk mendapatkan data mentah dan memprosesnya ke dalam struktur kedudukan yang ditakrifkan dalam platform FMZ, dan kemudian mengembalikannya.

Apabila fungsi berikut dipanggil dalam strategi, formatRpcPermintaan yang diterima oleh program pemalam sedikit berbeza dari antara muka lain.RpcRequestPerbezaan utama adalah bahawa nilai param adalah struktur komposit.

SetContractType Tetapkan kod kontrak.

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

SetDirection Menetapkan arah untuk meletakkan pesanan niaga hadapan.

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

SetMarginLevel Menetapkan leverage niaga hadapan.

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

DapatkanPosisi Dapatkan kedudukan niaga hadapan. Bilakahexchange.GetPosition()dipanggil:

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

Bilakahexchange.GetPosition("swap")dipanggil:

{"access_key":"123","method":"io","nonce":1623308734967442000,"params":{"args":["swap"],"code":3},"secret_key":"123"}
  • Lengkap Bahasa Go Contoh Plugin Protokol Sesuai (Akses ke 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)
}

Lebih lanjut